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