update_engine: UM: Add check for monotonic time elapsed.

This forks the former EvaluationStatus::IsTimeGreaterThan() into two
separate variants, checking either the wallclock or monotonic current
time against a corresponding timestamp. This is needed for policies that
require resilience against wallclock time volatility.

BUG=chromium:394778
TEST=Unit tests.

Change-Id: I9ecd20cc87a3a520e119f157e55ae4f54104a506
Reviewed-on: https://chromium-review.googlesource.com/209487
Commit-Queue: Gilad Arnold <[email protected]>
Tested-by: Gilad Arnold <[email protected]>
Reviewed-by: Gilad Arnold <[email protected]>
diff --git a/update_manager/chromeos_policy.cc b/update_manager/chromeos_policy.cc
index cba74e2..c49e3a7 100644
--- a/update_manager/chromeos_policy.cc
+++ b/update_manager/chromeos_policy.cc
@@ -211,7 +211,7 @@
       EvalStatus::kSucceeded) {
     return EvalStatus::kFailed;
   }
-  if (!ec->IsTimeGreaterThan(next_update_check))
+  if (!ec->IsWallclockTimeGreaterThan(next_update_check))
     return EvalStatus::kAskMeAgainLater;
 
   // It is time to check for an update.
@@ -604,7 +604,7 @@
   // the update, then no wait is needed.
   Time wait_expires = (update_state.first_seen +
                        min(wait_period, update_state.scatter_wait_period_max));
-  if (ec->IsTimeGreaterThan(wait_expires))
+  if (ec->IsWallclockTimeGreaterThan(wait_expires))
     wait_period = kZeroInterval;
 
   // Step 2: Maintain the update check threshold count.
diff --git a/update_manager/chromeos_policy.h b/update_manager/chromeos_policy.h
index c954f9d..889adbd 100644
--- a/update_manager/chromeos_policy.h
+++ b/update_manager/chromeos_policy.h
@@ -95,6 +95,10 @@
 
   // A private policy implementation returning the wallclock timestamp when
   // the next update check should happen.
+  // TODO(garnold) We should probably change that to infer a monotonic
+  // timestamp, which will make the update check intervals more resilient to
+  // clock skews. Might require switching some of the variables exported by the
+  // UpdaterProvider to report monotonic time, as well.
   EvalStatus NextUpdateCheckTime(EvaluationContext* ec, State* state,
                                  std::string* error,
                                  base::Time* next_update_check) const;
diff --git a/update_manager/evaluation_context.cc b/update_manager/evaluation_context.cc
index 2f827c1..2d5d705 100644
--- a/update_manager/evaluation_context.cc
+++ b/update_manager/evaluation_context.cc
@@ -20,6 +20,31 @@
 using chromeos_update_engine::ClockInterface;
 using std::string;
 
+namespace {
+
+// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
+// |ref_time| is sooner than the current value of |*reeval_time|, in which case
+// the latter is updated to the former.
+bool IsTimeGreaterThanHelper(base::Time ref_time, base::Time curr_time,
+                             base::Time* reeval_time) {
+  if (curr_time > ref_time)
+    return true;
+  // Remember the nearest reference we've checked against in this evaluation.
+  if (*reeval_time > ref_time)
+    *reeval_time = ref_time;
+  return false;
+}
+
+// If |expires| never happens (maximal value), returns the maximal interval;
+// otherwise, returns the difference between |expires| and |curr|.
+TimeDelta GetTimeout(base::Time curr, base::Time expires) {
+  if (expires.is_max())
+    return TimeDelta::Max();
+  return expires - curr;
+}
+
+}  // namespace
+
 namespace chromeos_update_manager {
 
 EvaluationContext::EvaluationContext(ClockInterface* clock,
@@ -80,23 +105,22 @@
     callback->Run();
 }
 
-bool EvaluationContext::IsTimeGreaterThan(base::Time timestamp) {
-  if (evaluation_start_ > timestamp)
-    return true;
-  // We need to keep track of these calls to trigger a reevaluation.
-  if (reevaluation_time_ > timestamp)
-    reevaluation_time_ = timestamp;
-  return false;
+bool EvaluationContext::IsWallclockTimeGreaterThan(base::Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
+                                 &reevaluation_time_wallclock_);
+}
+
+bool EvaluationContext::IsMonotonicTimeGreaterThan(base::Time timestamp) {
+  return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
+                                 &reevaluation_time_monotonic_);
 }
 
 void EvaluationContext::ResetEvaluation() {
-  // It is not important if these two values are not in sync. The first value is
-  // a reference in time when the evaluation started, to device time-based
-  // values for the current evaluation. The second is a deadline for the
-  // evaluation which required a monotonic source of time.
-  evaluation_start_ = clock_->GetWallclockTime();
+  evaluation_start_wallclock_ = clock_->GetWallclockTime();
+  evaluation_start_monotonic_ = clock_->GetMonotonicTime();
+  reevaluation_time_wallclock_ = Time::Max();
+  reevaluation_time_monotonic_ = Time::Max();
   evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
-  reevaluation_time_ = Time::Max();
 
   // Remove the cached values of non-const variables
   for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
@@ -121,14 +145,15 @@
     return false;
   }
 
-  TimeDelta timeout(TimeDelta::Max());
-  bool waiting_for_value_change = false;
-
-  // Handle reevaluation due to a IsTimeGreaterThan() call.
-  if (!reevaluation_time_.is_max())
-    timeout = reevaluation_time_ - evaluation_start_;
+  // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
+  // choose the smaller of the differences between evaluation start time and
+  // reevaluation time among the wallclock and monotonic scales.
+  TimeDelta timeout = std::min(
+      GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
+      GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
 
   // Handle reevaluation due to async or poll variables.
+  bool waiting_for_value_change = false;
   for (auto& it : value_cache_) {
     switch (it.first->GetMode()) {
       case kVariableModeAsync:
@@ -180,8 +205,12 @@
 
   base::DictionaryValue value;
   value.Set("variables", variables);  // Adopts |variables|.
-  value.SetString("evaluation_start",
-                  chromeos_update_engine::utils::ToString(evaluation_start_));
+  value.SetString(
+      "evaluation_start_wallclock",
+      chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
+  value.SetString(
+      "evaluation_start_monotonic",
+      chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
 
   string json_str;
   base::JSONWriter::WriteWithOptions(&value,
diff --git a/update_manager/evaluation_context.h b/update_manager/evaluation_context.h
index 9ed6f26..909201e 100644
--- a/update_manager/evaluation_context.h
+++ b/update_manager/evaluation_context.h
@@ -28,7 +28,8 @@
 // same policy request (an AsyncPolicyRequest might involve several
 // re-evaluations). Each evaluation of the EvaluationContext is run at a given
 // point in time, which is used as a reference for the evaluation timeout and
-// the time based queries of the policy, such as IsTimeGreaterThan().
+// the time based queries of the policy, such as
+// Is{Wallclock,Monotonic}TimeGreaterThan().
 //
 // Example:
 //
@@ -68,10 +69,11 @@
   template<typename T>
   const T* GetValue(Variable<T>* var);
 
-  // Returns whether the passed |timestamp| is less than the evaluation time.
-  // The |timestamp| value should be in the same scale as the values returned by
-  // ClockInterface::GetWallclockTime().
-  bool IsTimeGreaterThan(base::Time timestamp);
+  // Returns whether the evaluation time has surpassed |timestamp|, on either
+  // the ClockInterface::GetWallclockTime() or
+  // ClockInterface::GetMonotonicTime() scales, respectively.
+  bool IsWallclockTimeGreaterThan(base::Time timestamp);
+  bool IsMonotonicTimeGreaterThan(base::Time timestamp);
 
   // Returns whether the evaluation context has expired.
   bool is_expired() const { return is_expired_; }
@@ -150,17 +152,21 @@
   // Pointer to the mockable clock interface;
   chromeos_update_engine::ClockInterface* const clock_;
 
-  // The timestamp when the evaluation of this EvaluationContext started. This
-  // value is reset every time ResetEvaluation() is called. The time source
-  // used is the ClockInterface::GetWallclockTime().
-  base::Time evaluation_start_;
+  // The timestamps when the evaluation of this EvaluationContext started,
+  // corresponding to ClockInterface::GetWallclockTime() and
+  // ClockInterface::GetMonotonicTime(), respectively. These values are reset
+  // every time ResetEvaluation() is called.
+  base::Time evaluation_start_wallclock_;
+  base::Time evaluation_start_monotonic_;
 
-  // The timestamp measured on the GetWallclockTime() scale, when a reevaluation
-  // should be triggered due to IsTimeGreaterThan() calls value changes. This
-  // timestamp is greater or equal to |evaluation_start_| since it is a
-  // timestamp in the future, but it can be lower than the current
-  // GetWallclockTime() at some point of the evaluation.
-  base::Time reevaluation_time_;
+  // The timestamps when a reevaluation should be triggered due to various
+  // expected value changes, corresponding to ClockInterface::GetWallclockTime()
+  // and ClockInterface::GetMonotonicTIme(), respectively. These timestamps are
+  // greater or equal to corresponding |evaluation_start_{wallclock,monotonic}_|
+  // counterparts since they are in the future; however, they may be smaller
+  // than the current corresponding times during the course of evaluation.
+  base::Time reevaluation_time_wallclock_;
+  base::Time reevaluation_time_monotonic_;
 
   // The timeout of an evaluation.
   const base::TimeDelta evaluation_timeout_;
diff --git a/update_manager/evaluation_context_unittest.cc b/update_manager/evaluation_context_unittest.cc
index eafb219..52755fc 100644
--- a/update_manager/evaluation_context_unittest.cc
+++ b/update_manager/evaluation_context_unittest.cc
@@ -69,9 +69,9 @@
 class UmEvaluationContextTest : public ::testing::Test {
  protected:
   virtual void SetUp() {
-    // Set the clock to a fixed values.
-    fake_clock_.SetMonotonicTime(Time::FromInternalValue(12345678L));
-    // Mar 2, 2006 1:23:45 UTC is 1141262625 since the Unix Epoch.
+    // Apr 22, 2009 19:25:00 UTC (this is a random reference point).
+    fake_clock_.SetMonotonicTime(Time::FromTimeT(1240428300));
+    // Mar 2, 2006 1:23:45 UTC.
     fake_clock_.SetWallclockTime(Time::FromTimeT(1141262625));
     eval_ctx_ = new EvaluationContext(&fake_clock_, default_timeout_,
                                       default_timeout_);
@@ -337,47 +337,94 @@
   UMTEST_EXPECT_NULL(eval_ctx_->GetValue(&mock_var_async_));
 }
 
-TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimes) {
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesWallclock) {
   base::Time cur_time = fake_clock_.GetWallclockTime();
   // Advance the time on the clock but don't call ResetEvaluation yet.
   fake_clock_.SetWallclockTime(cur_time + TimeDelta::FromSeconds(4));
 
-  EXPECT_TRUE(eval_ctx_->IsTimeGreaterThan(cur_time -
-                                           TimeDelta::FromSeconds(1)));
-  EXPECT_FALSE(eval_ctx_->IsTimeGreaterThan(cur_time));
-  EXPECT_FALSE(eval_ctx_->IsTimeGreaterThan(cur_time +
-                                            TimeDelta::FromSeconds(1)));
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
   // Call ResetEvaluation now, which should use the new evaluation time.
   eval_ctx_->ResetEvaluation();
 
   cur_time = fake_clock_.GetWallclockTime();
-  EXPECT_TRUE(eval_ctx_->IsTimeGreaterThan(cur_time -
-                                           TimeDelta::FromSeconds(1)));
-  EXPECT_FALSE(eval_ctx_->IsTimeGreaterThan(cur_time));
-  EXPECT_FALSE(eval_ctx_->IsTimeGreaterThan(cur_time +
-                                            TimeDelta::FromSeconds(1)));
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
 }
 
-TEST_F(UmEvaluationContextTest, IsTimeGreaterThanSignalsTriggerReevaluation) {
-  EXPECT_FALSE(eval_ctx_->IsTimeGreaterThan(
+TEST_F(UmEvaluationContextTest, ResetEvaluationResetsTimesMonotonic) {
+  base::Time cur_time = fake_clock_.GetMonotonicTime();
+  // Advance the time on the clock but don't call ResetEvaluation yet.
+  fake_clock_.SetMonotonicTime(cur_time + TimeDelta::FromSeconds(4));
+
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+  // Call ResetEvaluation now, which should use the new evaluation time.
+  eval_ctx_->ResetEvaluation();
+
+  cur_time = fake_clock_.GetMonotonicTime();
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time - TimeDelta::FromSeconds(1)));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(cur_time));
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+          cur_time + TimeDelta::FromSeconds(1)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsWallclockTimeGreaterThan(
       fake_clock_.GetWallclockTime() + TimeDelta::FromSeconds(1)));
 
-  // The "false" from IsTimeGreaterThan means that's not that timestamp yet, so
-  // this should schedule a callback for when that happens.
+  // The "false" from IsWallclockTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
   EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
 }
 
-TEST_F(UmEvaluationContextTest, IsTimeGreaterThanDoesntRecordPastTimestamps) {
-  // IsTimeGreaterThan() should ignore timestamps on the past for reevaluation.
-  EXPECT_TRUE(eval_ctx_->IsTimeGreaterThan(
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanSignalsTriggerReevaluation) {
+  EXPECT_FALSE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() + TimeDelta::FromSeconds(1)));
+
+  // The "false" from IsMonotonicTimeGreaterThan means that's not that timestamp
+  // yet, so this should schedule a callback for when that happens.
+  EXPECT_TRUE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
+TEST_F(UmEvaluationContextTest,
+       IsWallclockTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsWallclockTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
       fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(20)));
-  EXPECT_TRUE(eval_ctx_->IsTimeGreaterThan(
+  EXPECT_TRUE(eval_ctx_->IsWallclockTimeGreaterThan(
       fake_clock_.GetWallclockTime() - TimeDelta::FromSeconds(1)));
 
   // Callback should not be scheduled.
   EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
 }
 
+TEST_F(UmEvaluationContextTest,
+       IsMonotonicTimeGreaterThanDoesntRecordPastTimestamps) {
+  // IsMonotonicTimeGreaterThan() should ignore timestamps on the past for
+  // reevaluation.
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(20)));
+  EXPECT_TRUE(eval_ctx_->IsMonotonicTimeGreaterThan(
+      fake_clock_.GetMonotonicTime() - TimeDelta::FromSeconds(1)));
+
+  // Callback should not be scheduled.
+  EXPECT_FALSE(eval_ctx_->RunOnValueChangeOrTimeout(Bind(&DoNothing)));
+}
+
 TEST_F(UmEvaluationContextTest, DumpContext) {
   // |fail_var_| yield "(no value)" since it is unset.
   eval_ctx_->GetValue(&fail_var_);
@@ -391,10 +438,11 @@
   eval_ctx_->GetValue(&fake_poll_var_);
 
   // Note that the variables are printed in alphabetical order. Also
-  // see UmEvaluationContextText::SetUp() where the value used for
-  // |evaluation_start| is set.
+  // see UmEvaluationContextText::SetUp() where the values used for
+  // |evaluation_start_{monotonic,wallclock| are set.
   EXPECT_EQ("{\n"
-            "   \"evaluation_start\": \"3/2/2006 1:23:45 GMT\",\n"
+            "   \"evaluation_start_monotonic\": \"4/22/2009 19:25:00 GMT\",\n"
+            "   \"evaluation_start_wallclock\": \"3/2/2006 1:23:45 GMT\",\n"
             "   \"variables\": {\n"
             "      \"fail_var\": \"(no value)\",\n"
             "      \"fake_int\": \"42\",\n"
diff --git a/update_manager/update_manager_unittest.cc b/update_manager/update_manager_unittest.cc
index 8f81be5..048a0d9 100644
--- a/update_manager/update_manager_unittest.cc
+++ b/update_manager/update_manager_unittest.cc
@@ -122,7 +122,7 @@
     // non-constant dependency. The threshold is far enough in the future to
     // ensure that it does not fire immediately.
     if (time_threshold_ < base::Time::Max())
-      ec->IsTimeGreaterThan(time_threshold_);
+      ec->IsWallclockTimeGreaterThan(time_threshold_);
     return EvalStatus::kAskMeAgainLater;
   }