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/strings/string_util.h>
28 #include <base/values.h>
29
30 #include "update_engine/common/utils.h"
31
32 using base::Callback;
33 using base::Closure;
34 using base::Time;
35 using base::TimeDelta;
36 using brillo::MessageLoop;
37 using chromeos_update_engine::ClockInterface;
38 using std::string;
39 using std::unique_ptr;
40
41 namespace {
42
43 // Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
44 // |ref_time| is sooner than the current value of |*reeval_time|, in which case
45 // the latter is updated to the former.
IsTimeGreaterThanHelper(Time ref_time,Time curr_time,Time * reeval_time)46 bool IsTimeGreaterThanHelper(Time ref_time, Time curr_time, 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|.
GetTimeout(Time curr,Time expires)57 TimeDelta GetTimeout(Time curr, Time expires) {
58 if (expires.is_max())
59 return TimeDelta::Max();
60 return expires - curr;
61 }
62
63 } // namespace
64
65 namespace chromeos_update_manager {
66
EvaluationContext(ClockInterface * clock,TimeDelta evaluation_timeout,TimeDelta expiration_timeout,unique_ptr<Callback<void (EvaluationContext *)>> unregister_cb)67 EvaluationContext::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
~EvaluationContext()81 EvaluationContext::~EvaluationContext() {
82 RemoveObserversAndTimeout();
83 if (unregister_cb_.get())
84 unregister_cb_->Run(this);
85 }
86
RemoveObserversAndTimeout()87 unique_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 std::move(callback_);
96 }
97
RemainingTime(Time monotonic_deadline) const98 TimeDelta 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
MonotonicDeadline(TimeDelta timeout)105 Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
106 return (timeout.is_max() ? Time::Max()
107 : clock_->GetMonotonicTime() + timeout);
108 }
109
ValueChanged(BaseVariable * var)110 void EvaluationContext::ValueChanged(BaseVariable* var) {
111 DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
112 OnValueChangedOrTimeout();
113 }
114
OnTimeout()115 void 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
OnValueChangedOrTimeout()123 void 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
IsWallclockTimeGreaterThan(Time timestamp)131 bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
132 return IsTimeGreaterThanHelper(
133 timestamp, evaluation_start_wallclock_, &reevaluation_time_wallclock_);
134 }
135
IsMonotonicTimeGreaterThan(Time timestamp)136 bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
137 return IsTimeGreaterThanHelper(
138 timestamp, evaluation_start_monotonic_, &reevaluation_time_monotonic_);
139 }
140
ResetEvaluation()141 void 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
ResetExpiration()158 void EvaluationContext::ResetExpiration() {
159 expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
160 is_expired_ = false;
161 }
162
RunOnValueChangeOrTimeout(Closure callback)163 bool 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
DumpContext() const229 string EvaluationContext::DumpContext() const {
230 auto variables = std::make_unique<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", std::move(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