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 33 using base::Callback; 34 using base::Closure; 35 using base::Time; 36 using base::TimeDelta; 37 using brillo::MessageLoop; 38 using chromeos_update_engine::ClockInterface; 39 using std::string; 40 using std::unique_ptr; 41 42 namespace { 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. 47 bool 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|. 59 TimeDelta GetTimeout(Time curr, Time expires) { 60 if (expires.is_max()) 61 return TimeDelta::Max(); 62 return expires - curr; 63 } 64 65 } // namespace 66 67 namespace chromeos_update_manager { 68 69 EvaluationContext::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 83 EvaluationContext::~EvaluationContext() { 84 RemoveObserversAndTimeout(); 85 if (unregister_cb_.get()) 86 unregister_cb_->Run(this); 87 } 88 89 unique_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 100 TimeDelta 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 107 Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) { 108 return (timeout.is_max() ? Time::Max() : 109 clock_->GetMonotonicTime() + timeout); 110 } 111 112 void EvaluationContext::ValueChanged(BaseVariable* var) { 113 DLOG(INFO) << "ValueChanged() called for variable " << var->GetName(); 114 OnValueChangedOrTimeout(); 115 } 116 117 void 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 125 void 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 133 bool EvaluationContext::IsWallclockTimeGreaterThan(Time timestamp) { 134 return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_, 135 &reevaluation_time_wallclock_); 136 } 137 138 bool EvaluationContext::IsMonotonicTimeGreaterThan(Time timestamp) { 139 return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_, 140 &reevaluation_time_monotonic_); 141 } 142 143 void 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 160 void EvaluationContext::ResetExpiration() { 161 expiration_monotonic_deadline_ = MonotonicDeadline(expiration_timeout_); 162 is_expired_ = false; 163 } 164 165 bool 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 231 string 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