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.
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|.
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 
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 
81 EvaluationContext::~EvaluationContext() {
82   RemoveObserversAndTimeout();
83   if (unregister_cb_.get())
84     unregister_cb_->Run(this);
85 }
86 
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 
98 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 
105 Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
106   return (timeout.is_max() ? Time::Max()
107                            : clock_->GetMonotonicTime() + timeout);
108 }
109 
110 void EvaluationContext::ValueChanged(BaseVariable* var) {
111   DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
112   OnValueChangedOrTimeout();
113 }
114 
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 
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 
131 bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) {
132   return IsTimeGreaterThanHelper(
133       timestamp, evaluation_start_wallclock_, &reevaluation_time_wallclock_);
134 }
135 
136 bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) {
137   return IsTimeGreaterThanHelper(
138       timestamp, evaluation_start_monotonic_, &reevaluation_time_monotonic_);
139 }
140 
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 
158 void EvaluationContext::ResetExpiration() {
159   expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_);
160   is_expired_ = false;
161 }
162 
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 
229 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