PolicyManager: Schedule re-evaluations based on variable usage.

This patch makes the EvaluationContext re-schedule a policy request
based on the variables used by that method, waiting for the Async
variables and polling the Poll variables on the suggested interval.

In order to use the main loop functions from the EvaluationContext
they were moved to its own file called event_loop.h.

BUG=chromium:340871
TEST=Unit tests added.

Change-Id: Ibfc52e4dfd12c5e1ef87b5ad9cc318f9821dcfdd
Reviewed-on: https://chromium-review.googlesource.com/190424
Reviewed-by: Gilad Arnold <[email protected]>
Commit-Queue: Alex Deymo <[email protected]>
Tested-by: Alex Deymo <[email protected]>
diff --git a/policy_manager/evaluation_context.cc b/policy_manager/evaluation_context.cc
index 062ed26..c756ddf 100644
--- a/policy_manager/evaluation_context.cc
+++ b/policy_manager/evaluation_context.cc
@@ -4,14 +4,99 @@
 
 #include "update_engine/policy_manager/evaluation_context.h"
 
+#include <base/bind.h>
+
+using base::Closure;
 using base::TimeDelta;
 
 namespace chromeos_policy_manager {
 
+EvaluationContext::~EvaluationContext() {
+  RemoveObserversAndTimeout();
+}
+
+void EvaluationContext::RemoveObserversAndTimeout() {
+  for (auto& it : value_cache_) {
+    if (it.first->GetMode() == kVariableModeAsync)
+      it.first->RemoveObserver(this);
+  }
+  CancelMainLoopEvent(poll_timeout_event_);
+  poll_timeout_event_ = kEventIdNull;
+}
+
 TimeDelta EvaluationContext::RemainingTime() const {
   // TODO(deymo): Return a timeout based on the elapsed time on the current
   // policy request evaluation.
   return TimeDelta::FromSeconds(1.);
 }
 
+void EvaluationContext::ValueChanged(BaseVariable* var) {
+  DLOG(INFO) << "ValueChanged for variable " << var->GetName();
+  OnValueChangedOrPollTimeout();
+}
+
+void EvaluationContext::OnPollTimeout() {
+  DLOG(INFO) << "OnPollTimeout() called.";
+  poll_timeout_event_ = kEventIdNull;
+  OnValueChangedOrPollTimeout();
+}
+
+void EvaluationContext::OnValueChangedOrPollTimeout() {
+  RemoveObserversAndTimeout();
+  // Remove the cached values of non-const variables
+  for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
+    if (it->first->GetMode() == kVariableModeConst) {
+      ++it;
+    } else {
+      it = value_cache_.erase(it);
+    }
+  }
+
+  if (value_changed_callback_.get() != NULL) {
+    value_changed_callback_->Run();
+    value_changed_callback_.reset();
+  }
+}
+
+bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
+  TimeDelta reeval_timeout;
+  bool reeval_timeout_set = false;
+  bool waiting_for_value_change = false;
+
+  if (value_changed_callback_.get() != NULL) {
+    LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
+    return false;
+  }
+
+  for (auto& it : value_cache_) {
+    switch (it.first->GetMode()) {
+      case kVariableModeAsync:
+        waiting_for_value_change = true;
+        DLOG(INFO) << "Waiting for value on " << it.first->GetName();
+        it.first->AddObserver(this);
+        break;
+      case kVariableModePoll:
+        if (!reeval_timeout_set || reeval_timeout > it.first->GetPollInterval())
+          reeval_timeout = it.first->GetPollInterval();
+        reeval_timeout_set = true;
+        break;
+      case kVariableModeConst:
+        // Ignored.
+        break;
+    }
+  }
+  // Check if the re-evaluation is actually being scheduled. If there are no
+  // events waited for, this function should return false.
+  if (!waiting_for_value_change && !reeval_timeout_set)
+    return false;
+  if (reeval_timeout_set) {
+    poll_timeout_event_ = RunFromMainLoopAfterTimeout(
+        base::Bind(&EvaluationContext::OnPollTimeout, this),  // refcounted.
+        reeval_timeout);
+  }
+
+  value_changed_callback_.reset(new Closure(callback));
+  return true;
+}
+
 }  // namespace chromeos_policy_manager