Home | History | Annotate | Download | only in update_manager
      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