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 31 using base::Callback; 32 using base::Closure; 33 using base::Time; 34 using base::TimeDelta; 35 using brillo::MessageLoop; 36 using chromeos_update_engine::ClockInterface; 37 using std::string; 38 using std::unique_ptr; 39 40 namespace { 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. 45 bool 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|. 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 unique_ptr<Closure>(callback_.release()); 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(timestamp, evaluation_start_wallclock_, 133 &reevaluation_time_wallclock_); 134 } 135 136 bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) { 137 return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_, 138 &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 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