blob: 2d5d7053d9c267477280813f634e40d790df7db8 [file] [log] [blame]
// Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "update_engine/update_manager/evaluation_context.h"
#include <algorithm>
#include <string>
#include <base/bind.h>
#include <base/json/json_writer.h>
#include <base/strings/string_util.h>
#include <base/values.h>
#include "update_engine/utils.h"
using base::Closure;
using base::Time;
using base::TimeDelta;
using chromeos_update_engine::ClockInterface;
using std::string;
namespace {
// Returns whether |curr_time| surpassed |ref_time|; if not, also checks whether
// |ref_time| is sooner than the current value of |*reeval_time|, in which case
// the latter is updated to the former.
bool IsTimeGreaterThanHelper(base::Time ref_time, base::Time curr_time,
base::Time* reeval_time) {
if (curr_time > ref_time)
return true;
// Remember the nearest reference we've checked against in this evaluation.
if (*reeval_time > ref_time)
*reeval_time = ref_time;
return false;
}
// If |expires| never happens (maximal value), returns the maximal interval;
// otherwise, returns the difference between |expires| and |curr|.
TimeDelta GetTimeout(base::Time curr, base::Time expires) {
if (expires.is_max())
return TimeDelta::Max();
return expires - curr;
}
} // namespace
namespace chromeos_update_manager {
EvaluationContext::EvaluationContext(ClockInterface* clock,
TimeDelta evaluation_timeout,
TimeDelta expiration_timeout)
: clock_(clock),
evaluation_timeout_(evaluation_timeout),
expiration_monotonic_deadline_(MonotonicDeadline(expiration_timeout)),
weak_ptr_factory_(this) {
ResetEvaluation();
}
EvaluationContext::~EvaluationContext() {
RemoveObserversAndTimeout();
}
void EvaluationContext::RemoveObserversAndTimeout() {
for (auto& it : value_cache_) {
if (it.first->GetMode() == kVariableModeAsync)
it.first->RemoveObserver(this);
}
CancelMainLoopEvent(timeout_event_);
timeout_event_ = kEventIdNull;
}
TimeDelta EvaluationContext::RemainingTime(Time monotonic_deadline) const {
if (monotonic_deadline.is_max())
return TimeDelta::Max();
TimeDelta remaining = monotonic_deadline - clock_->GetMonotonicTime();
return std::max(remaining, TimeDelta());
}
Time EvaluationContext::MonotonicDeadline(TimeDelta timeout) {
return (timeout.is_max() ? Time::Max() :
clock_->GetMonotonicTime() + timeout);
}
void EvaluationContext::ValueChanged(BaseVariable* var) {
DLOG(INFO) << "ValueChanged() called for variable " << var->GetName();
OnValueChangedOrTimeout();
}
void EvaluationContext::OnTimeout() {
DLOG(INFO) << "OnTimeout() called due to "
<< (timeout_marks_expiration_ ? "expiration" : "poll interval");
timeout_event_ = kEventIdNull;
is_expired_ = timeout_marks_expiration_;
OnValueChangedOrTimeout();
}
void EvaluationContext::OnValueChangedOrTimeout() {
RemoveObserversAndTimeout();
// Copy the callback handle locally, allowing it to be reassigned.
scoped_ptr<Closure> callback(callback_.release());
if (callback.get())
callback->Run();
}
bool EvaluationContext::IsWallclockTimeGreaterThan(base::Time timestamp) {
return IsTimeGreaterThanHelper(timestamp, evaluation_start_wallclock_,
&reevaluation_time_wallclock_);
}
bool EvaluationContext::IsMonotonicTimeGreaterThan(base::Time timestamp) {
return IsTimeGreaterThanHelper(timestamp, evaluation_start_monotonic_,
&reevaluation_time_monotonic_);
}
void EvaluationContext::ResetEvaluation() {
evaluation_start_wallclock_ = clock_->GetWallclockTime();
evaluation_start_monotonic_ = clock_->GetMonotonicTime();
reevaluation_time_wallclock_ = Time::Max();
reevaluation_time_monotonic_ = Time::Max();
evaluation_monotonic_deadline_ = MonotonicDeadline(evaluation_timeout_);
// Remove the cached values of non-const variables
for (auto it = value_cache_.begin(); it != value_cache_.end(); ) {
if (it->first->GetMode() == kVariableModeConst) {
++it;
} else {
it = value_cache_.erase(it);
}
}
}
bool EvaluationContext::RunOnValueChangeOrTimeout(Closure callback) {
// Check that the method was not called more than once.
if (callback_.get() != nullptr) {
LOG(ERROR) << "RunOnValueChangeOrTimeout called more than once.";
return false;
}
// Check that the context did not yet expire.
if (is_expired()) {
LOG(ERROR) << "RunOnValueChangeOrTimeout called on an expired context.";
return false;
}
// Handle reevaluation due to a Is{Wallclock,Monotonic}TimeGreaterThan(). We
// choose the smaller of the differences between evaluation start time and
// reevaluation time among the wallclock and monotonic scales.
TimeDelta timeout = std::min(
GetTimeout(evaluation_start_wallclock_, reevaluation_time_wallclock_),
GetTimeout(evaluation_start_monotonic_, reevaluation_time_monotonic_));
// Handle reevaluation due to async or poll variables.
bool waiting_for_value_change = false;
for (auto& it : value_cache_) {
switch (it.first->GetMode()) {
case kVariableModeAsync:
DLOG(INFO) << "Waiting for value on " << it.first->GetName();
it.first->AddObserver(this);
waiting_for_value_change = true;
break;
case kVariableModePoll:
timeout = std::min(timeout, it.first->GetPollInterval());
break;
case kVariableModeConst:
// Ignored.
break;
}
}
// Check if the re-evaluation is actually being scheduled. If there are no
// events waited for, this function should return false.
if (!waiting_for_value_change && timeout.is_max())
return false;
// Ensure that we take into account the expiration timeout.
TimeDelta expiration = RemainingTime(expiration_monotonic_deadline_);
timeout_marks_expiration_ = expiration < timeout;
if (timeout_marks_expiration_)
timeout = expiration;
// Store the reevaluation callback.
callback_.reset(new Closure(callback));
// Schedule a timeout event, if one is set.
if (!timeout.is_max()) {
DLOG(INFO) << "Waiting for timeout in "
<< chromeos_update_engine::utils::FormatTimeDelta(timeout);
timeout_event_ = RunFromMainLoopAfterTimeout(
base::Bind(&EvaluationContext::OnTimeout,
weak_ptr_factory_.GetWeakPtr()),
timeout);
}
return true;
}
string EvaluationContext::DumpContext() const {
base::DictionaryValue* variables = new base::DictionaryValue();
for (auto& it : value_cache_) {
variables->SetString(it.first->GetName(), it.second.ToString());
}
base::DictionaryValue value;
value.Set("variables", variables); // Adopts |variables|.
value.SetString(
"evaluation_start_wallclock",
chromeos_update_engine::utils::ToString(evaluation_start_wallclock_));
value.SetString(
"evaluation_start_monotonic",
chromeos_update_engine::utils::ToString(evaluation_start_monotonic_));
string json_str;
base::JSONWriter::WriteWithOptions(&value,
base::JSONWriter::OPTIONS_PRETTY_PRINT,
&json_str);
base::TrimWhitespaceASCII(json_str, base::TRIM_TRAILING, &json_str);
return json_str;
}
} // namespace chromeos_update_manager