1//
2// Copyright (C) 2014 The Android Open Source Project
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//      http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15//
16
17#include "update_engine/update_manager/evaluation_context.h"
18
19#include <algorithm>
20#include <memory>
21#include <string>
22
23#include <base/bind.h>
24#include <base/json/json_writer.h>
25#include <base/location.h>
26#include <base/strings/string_util.h>
27#include <base/values.h>
28
29#include "update_engine/common/utils.h"
30
31using base::Callback;
32using base::Closure;
33using base::Time;
34using base::TimeDelta;
35using brillo::MessageLoop;
36using chromeos_update_engine::ClockInterface;
37using std::string;
38using std::unique_ptr;
39
40namespace {
41
42// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
43// |ref_time| is sooner than the current value of |*reeval_time|, in which case
44// the latter is updated to the former.
45bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time,
46                             Time* reeval_time) {
47  if (curr_time > ref_time)
48    return true;
49  // Remember the nearest reference we've checked against in this evaluation.
50  if (*reeval_time > ref_time)
51    *reeval_time = ref_time;
52  return false;
53}
54
55// If |expires| never happens (maximal value), returns the maximal interval;
56// otherwise, returns the difference between |expires| and |curr|.
57TimeDelta GetTimeout(Time curr, Time expires) {
58  if (expires.is_max())
59    return TimeDelta::Max();
60  return expires - curr;
61}
62
63}  // namespace
64
65namespace chromeos_update_manager {
66
67EvaluationContext::EvaluationContext(
68    ClockInterface* clock,
69    TimeDelta evaluation_timeout,
70    TimeDelta expiration_timeout,
71    unique_ptr<Callback<void(EvaluationContext*)>> unregister_cb)
72    : clock_(clock),
73      evaluation_timeout_(evaluation_timeout),
74      expiration_timeout_(expiration_timeout),
75      unregister_cb_(std::move(unregister_cb)),
76      weak_ptr_factory_(this) {
77  ResetEvaluation();
78  ResetExpiration();
79}
80
81EvaluationContext::~EvaluationContext() {
82  RemoveObserversAndTimeout();
83  if (unregister_cb_.get())
84    unregister_cb_->Run(this);
85}
86
87unique_ptr<Closure> EvaluationContext::RemoveObserversAndTimeout() {
88  for (auto& it : value_cache_) {
89    if (it.first->GetMode() == kVariableModeAsync)
90      it.first->RemoveObserver(this);
91  }
92  MessageLoop::current()->CancelTask(timeout_event_);
93  timeout_event_ = MessageLoop::kTaskIdNull;
94
95  return unique_ptr<Closure>(callback_.release());
96}
97
98TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
99  if (monotonic_deadline.is_max())
100    return TimeDelta::Max();
101  TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
102  return std::max(remaining, TimeDelta());
103}
104
105Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
106  return (timeout.is_max() ? Time::Max() :
107          clock_->GetMonotonicTime() + timeout);
108}
109
110void EvaluationContext::ValueChanged(BaseVariable* var) {
111  DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
112  OnValueChangedOrTimeout();
113}
114
115void EvaluationContext::OnTimeout() {
116  DLOG(INFO) << "OnTimeout() called due to "
117             << (timeout_marks_expiration_ ? "expiration" : "poll interval");
118  timeout_event_ = MessageLoop::kTaskIdNull;
119  is_expired_ = timeout_marks_expiration_;
120  OnValueChangedOrTimeout();
121}
122
123void EvaluationContext::OnValueChangedOrTimeout() {
124  // Copy the callback handle locally, allowing it to be reassigned.
125  unique_ptr<Closure> callback = RemoveObserversAndTimeout();
126
127  if (callback.get())
128    callback->Run();
129}
130
131bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
132  return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
133                                 &reevaluation_time_wallclock_);
134}
135
136bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
137  return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
138                                 &reevaluation_time_monotonic_);
139}
140
141void EvaluationContext::ResetEvaluation() {
142  evaluation_start_wallclock_ = clock_->GetWallclockTime();
143  evaluation_start_monotonic_ = clock_->GetMonotonicTime();
144  reevaluation_time_wallclock_ = Time::Max();
145  reevaluation_time_monotonic_ = Time::Max();
146  evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
147
148  // Remove the cached values of non-const variables
149  for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
150    if (it->first->GetMode() == kVariableModeConst) {
151      ++it;
152    } else {
153      it = value_cache_.erase(it);
154    }
155  }
156}
157
158void EvaluationContext::ResetExpiration() {
159  expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
160  is_expired_ = false;
161}
162
163bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
164  // Check that the method was not called more than once.
165  if (callback_.get()) {
166    LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
167    return false;
168  }
169
170  // Check that the context did not yet expire.
171  if (is_expired()) {
172    LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
173    return false;
174  }
175
176  // Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
177  // choose the smaller of the differences between evaluation start time and
178  // reevaluation time among the wallclock and monotonic scales.
179  TimeDelta timeout = std::min(
180      GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
181      GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
182
183  // Handle reevaluation due to async or poll variables.
184  bool waiting_for_value_change = false;
185  for (auto& it : value_cache_) {
186    switch (it.first->GetMode()) {
187      case kVariableModeAsync:
188        DLOG(INFO) << "Waiting for value on " << it.first->GetName();
189        it.first->AddObserver(this);
190        waiting_for_value_change = true;
191        break;
192      case kVariableModePoll:
193        timeout = std::min(timeout, it.first->GetPollInterval());
194        break;
195      case kVariableModeConst:
196        // Ignored.
197        break;
198    }
199  }
200
201  // Check if the re-evaluation is actually being scheduled. If there are no
202  // events waited for, this function should return false.
203  if (!waiting_for_value_change && timeout.is_max())
204    return false;
205
206  // Ensure that we take into account the expiration timeout.
207  TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
208  timeout_marks_expiration_ = expiration < timeout;
209  if (timeout_marks_expiration_)
210    timeout = expiration;
211
212  // Store the reevaluation callback.
213  callback_.reset(new Closure(callback));
214
215  // Schedule a timeout event, if one is set.
216  if (!timeout.is_max()) {
217    DLOG(INFO) << "Waiting for timeout in "
218               << chromeos_update_engine::utils::FormatTimeDelta(timeout);
219    timeout_event_ = MessageLoop::current()->PostDelayedTask(
220        FROM_HERE,
221        base::Bind(&EvaluationContext::OnTimeout,
222                   weak_ptr_factory_.GetWeakPtr()),
223        timeout);
224  }
225
226  return true;
227}
228
229string EvaluationContext::DumpContext() const {
230  base::DictionaryValue* variables = new base::DictionaryValue();
231  for (auto& it : value_cache_) {
232    variables->SetString(it.first->GetName(), it.second.ToString());
233  }
234
235  base::DictionaryValue value;
236  value.Set("variables", variables);  // Adopts |variables|.
237  value.SetString(
238      "evaluation_start_wallclock",
239      chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
240  value.SetString(
241      "evaluation_start_monotonic",
242      chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
243
244  string json_str;
245  base::JSONWriter::WriteWithOptions(
246      value, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_str);
247  base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
248
249  return json_str;
250}
251
252}  // namespace chromeos_update_manager
253