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