Commit 27e044cf authored by tengs's avatar tengs Committed by Commit bot

Add SyncScheduler for scheduling CryptAuth enrollments and syncing devices.

BUG=420315
TEST=unit test

Review URL: https://codereview.chromium.org/1147563002

Cr-Commit-Position: refs/heads/master@{#330713}
parent f5966937
......@@ -439,6 +439,7 @@
'proximity_auth/cryptauth/cryptauth_client_impl_unittest.cc',
'proximity_auth/cryptauth/cryptauth_enroller_impl_unittest.cc',
'proximity_auth/cryptauth/fake_secure_message_delegate_unittest.cc',
'proximity_auth/cryptauth/sync_scheduler_impl_unittest.cc',
'proximity_auth/logging/logging_unittest.cc',
'proximity_auth/proximity_auth_system_unittest.cc',
'proximity_auth/remote_status_update_unittest.cc',
......
......@@ -123,6 +123,10 @@
"proximity_auth/cryptauth/cryptauth_enrollment_utils.h",
"proximity_auth/cryptauth/secure_message_delegate.cc",
"proximity_auth/cryptauth/secure_message_delegate.h",
"proximity_auth/cryptauth/sync_scheduler.cc",
"proximity_auth/cryptauth/sync_scheduler.h",
"proximity_auth/cryptauth/sync_scheduler_impl.cc",
"proximity_auth/cryptauth/sync_scheduler_impl.h",
],
'export_dependent_settings': [
'cryptauth_proto',
......
......@@ -21,6 +21,10 @@ source_set("cryptauth") {
"cryptauth_enrollment_utils.h",
"secure_message_delegate.cc",
"secure_message_delegate.h",
"sync_scheduler.cc",
"sync_scheduler.h",
"sync_scheduler_impl.cc",
"sync_scheduler_impl.h",
]
deps = [
......@@ -65,6 +69,7 @@ source_set("unit_tests") {
"cryptauth_client_impl_unittest.cc",
"cryptauth_enroller_impl_unittest.cc",
"fake_secure_message_delegate_unittest.cc",
"sync_scheduler_impl_unittest.cc",
]
deps = [
......
// Copyright 2015 The Chromium 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 "components/proximity_auth/cryptauth/sync_scheduler.h"
#include "components/proximity_auth/logging/logging.h"
namespace proximity_auth {
SyncScheduler::SyncRequest::SyncRequest(
base::WeakPtr<SyncScheduler> sync_scheduler)
: sync_scheduler_(sync_scheduler), completed_(false) {
}
SyncScheduler::SyncRequest::~SyncRequest() {
if (!completed_)
PA_LOG(ERROR) << "SyncRequest destroyed but Complete() was never called";
}
void SyncScheduler::SyncRequest::OnDidComplete(bool success) {
if (sync_scheduler_) {
sync_scheduler_->OnSyncCompleted(success);
sync_scheduler_.reset();
completed_ = true;
} else {
PA_LOG(ERROR) << "SyncRequest completed, but SyncScheduler destroyed.";
}
}
} // proximity_auth
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_SYNC_SCHEDULER_H
#define COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_SYNC_SCHEDULER_H
#include "base/memory/weak_ptr.h"
#include "base/time/time.h"
namespace proximity_auth {
// Interface for scheduling the next CryptAuth sync (e.g. enrollment or device
// sync). The scheduler has two different strategies affecting when to perform
// the next operation:
// PERIODIC_REFRESH: The last sync was made successfully, so we can wait a
// relatively long time before making another sync.
// AGGRESSIVE_RECOVERY: The last sync failed, so we try more aggressively to
// make enrollment attempts with subsequent backoff for repeated
// failures.
// A random jitter is applied to each sync period to smooth qps to the server.
class SyncScheduler {
public:
// The sync strategies mentioned in the class comments.
enum class Strategy { PERIODIC_REFRESH, AGGRESSIVE_RECOVERY };
// The states that the scheduler can be in.
enum class SyncState { NOT_STARTED, WAITING_FOR_REFRESH, SYNC_IN_PROGRESS };
// An instance is passed to the delegate when the scheduler fires for each
// sync attempt. The delegate should call |Complete()| when the sync succeeds
// or fails to resume the scheduler.
class SyncRequest {
public:
explicit SyncRequest(base::WeakPtr<SyncScheduler> sync_scheduler);
~SyncRequest();
void OnDidComplete(bool success);
protected:
// The parent scheduler that dispatched this request.
base::WeakPtr<SyncScheduler> sync_scheduler_;
// True if |OnDidComplete()| has been called.
bool completed_;
DISALLOW_COPY_AND_ASSIGN(SyncRequest);
};
// Handles the actual sync operation.
class Delegate {
public:
virtual ~Delegate() {}
// Called when the scheduler fires and requests a sync attempt. The delegate
// should call sync_request->Complete() when the request finishes.
virtual void OnSyncRequested(scoped_ptr<SyncRequest> sync_request) = 0;
};
virtual ~SyncScheduler() {}
// Starts the scheduler with an aggressive recovery strategy if
// |strategy| is true; otherwise, it will be started with
// periodic refresh.
//
// |elapsed_time_since_last_sync| is the time since the last successful sync,
// so we can determine the duration of the first sync period. For example, the
// scheduler will immediately issue a sync request if the elapsed time is
// greater than the refresh period.
virtual void Start(const base::TimeDelta& elapsed_time_since_last_sync,
Strategy strategy) = 0;
// Cancels the current scheduled sync, and forces a sync immediately. Note
// that if this sync fails, the scheduler will adopt the AGGRESSIVE_RECOVERY
// strategy.
virtual void ForceSync() = 0;
// Returns the time until the next scheduled sync operation. If no sync is
// scheduled, a TimeDelta of zero will be returned.
virtual base::TimeDelta GetTimeToNextSync() const = 0;
// Returns the current sync strategy.
virtual Strategy GetStrategy() const = 0;
// Returns the current state of the scheduler.
virtual SyncState GetSyncState() const = 0;
protected:
// Called by SyncRequest instances when the sync completes.
virtual void OnSyncCompleted(bool success) = 0;
};
} // namespace proximity_auth
#endif // COMPONENTS_PROXIMITY_CRYPTAUTH_SYNC_SCHEDULER_H
// Copyright 2015 The Chromium 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 "components/proximity_auth/cryptauth/sync_scheduler_impl.h"
#include <algorithm>
#include <cmath>
#include <limits>
#include "base/bind.h"
#include "base/numerics/safe_conversions.h"
#include "base/rand_util.h"
#include "base/strings/stringprintf.h"
#include "components/proximity_auth/logging/logging.h"
namespace proximity_auth {
namespace {
// Returns a human readable string given a |time_delta|.
std::string TimeDeltaToString(const base::TimeDelta& time_delta) {
if (time_delta.InDays() > 0)
return base::StringPrintf("%d days", time_delta.InDays());
if (time_delta.InHours() > 0)
return base::StringPrintf("%d hours", time_delta.InHours());
if (time_delta.InMinutes() > 0)
return base::StringPrintf("%d minutes", time_delta.InMinutes());
return base::StringPrintf("%d seconds",
base::saturated_cast<int>(time_delta.InSeconds()));
}
} // namespace
SyncSchedulerImpl::SyncSchedulerImpl(Delegate* delegate,
base::TimeDelta refresh_period,
base::TimeDelta base_recovery_period,
double max_jitter_ratio,
const std::string& scheduler_name)
: delegate_(delegate),
refresh_period_(refresh_period),
base_recovery_period_(base_recovery_period),
max_jitter_ratio_(max_jitter_ratio),
scheduler_name_(scheduler_name),
strategy_(Strategy::PERIODIC_REFRESH),
sync_state_(SyncState::NOT_STARTED),
weak_ptr_factory_(this) {
}
SyncSchedulerImpl::~SyncSchedulerImpl() {
}
void SyncSchedulerImpl::Start(
const base::TimeDelta& elapsed_time_since_last_sync,
Strategy strategy) {
strategy_ = strategy;
sync_state_ = SyncState::WAITING_FOR_REFRESH;
// We reset the failure backoff when the scheduler is started again, as the
// configuration that caused the previous attempts to fail most likely won't
// be present after a restart.
if (strategy_ == Strategy::AGGRESSIVE_RECOVERY)
failure_count_ = 1;
// To take into account the time waited when the system is powered off, we
// subtract the time elapsed with a normal sync period to the initial time
// to wait.
base::TimeDelta sync_delta =
GetJitteredPeriod() - elapsed_time_since_last_sync;
if (sync_delta < base::TimeDelta::FromSeconds(0))
sync_delta = base::TimeDelta::FromSeconds(0);
ScheduleNextSync(sync_delta);
}
void SyncSchedulerImpl::ForceSync() {
OnTimerFired();
}
base::TimeDelta SyncSchedulerImpl::GetTimeToNextSync() const {
if (!timer_)
return base::TimeDelta::FromSeconds(0);
return timer_->GetCurrentDelay();
}
SyncScheduler::Strategy SyncSchedulerImpl::GetStrategy() const {
return strategy_;
}
SyncScheduler::SyncState SyncSchedulerImpl::GetSyncState() const {
return sync_state_;
}
void SyncSchedulerImpl::OnTimerFired() {
timer_.reset();
if (strategy_ == Strategy::PERIODIC_REFRESH) {
PA_LOG(INFO) << "Timer fired for periodic refresh, making request...";
sync_state_ = SyncState::SYNC_IN_PROGRESS;
} else if (strategy_ == Strategy::AGGRESSIVE_RECOVERY) {
PA_LOG(INFO) << "Timer fired for aggressive recovery, making request...";
sync_state_ = SyncState::SYNC_IN_PROGRESS;
} else {
NOTREACHED();
return;
}
delegate_->OnSyncRequested(
make_scoped_ptr(new SyncRequest(weak_ptr_factory_.GetWeakPtr())));
}
scoped_ptr<base::Timer> SyncSchedulerImpl::CreateTimer() {
bool retain_user_task = false;
bool is_repeating = false;
return make_scoped_ptr(new base::Timer(retain_user_task, is_repeating));
}
void SyncSchedulerImpl::ScheduleNextSync(const base::TimeDelta& sync_delta) {
if (sync_state_ != SyncState::WAITING_FOR_REFRESH) {
PA_LOG(ERROR) << "Unexpected state when scheduling next sync: sync_state="
<< static_cast<int>(sync_state_);
return;
}
bool is_aggressive_recovery = (strategy_ == Strategy::AGGRESSIVE_RECOVERY);
PA_LOG(INFO) << "Scheduling next sync for " << scheduler_name_ << ":\n"
<< " Strategy: " << (is_aggressive_recovery
? "Aggressive Recovery"
: "Periodic Refresh") << "\n"
<< " Time Delta: " << TimeDeltaToString(sync_delta)
<< (is_aggressive_recovery
? base::StringPrintf(
"\n Previous Failures: %d",
base::saturated_cast<int>(failure_count_))
: "");
timer_ = CreateTimer();
timer_->Start(FROM_HERE, sync_delta,
base::Bind(&SyncSchedulerImpl::OnTimerFired,
weak_ptr_factory_.GetWeakPtr()));
}
void SyncSchedulerImpl::OnSyncCompleted(bool success) {
if (sync_state_ != SyncState::SYNC_IN_PROGRESS) {
PA_LOG(ERROR) << "Unexpected state when sync completed: sync_state="
<< static_cast<int>(sync_state_)
<< ", strategy_=" << static_cast<int>(strategy_);
return;
}
sync_state_ = SyncState::WAITING_FOR_REFRESH;
if (success) {
strategy_ = Strategy::PERIODIC_REFRESH;
failure_count_ = 0;
} else {
strategy_ = Strategy::AGGRESSIVE_RECOVERY;
++failure_count_;
}
ScheduleNextSync(GetJitteredPeriod());
}
base::TimeDelta SyncSchedulerImpl::GetJitteredPeriod() {
double jitter = 2 * max_jitter_ratio_ * (base::RandDouble() - 0.5);
base::TimeDelta period = GetPeriod();
base::TimeDelta jittered_time_delta = period + (period * jitter);
if (jittered_time_delta.InMilliseconds() < 0)
jittered_time_delta = base::TimeDelta::FromMilliseconds(0);
return jittered_time_delta;
}
base::TimeDelta SyncSchedulerImpl::GetPeriod() {
if (strategy_ == Strategy::PERIODIC_REFRESH) {
return refresh_period_;
} else if (strategy_ == Strategy::AGGRESSIVE_RECOVERY && failure_count_ > 0) {
// The backoff for each consecutive failure is exponentially doubled until
// it is equal to the normal refresh period.
// Note: |backoff_factor| may evaulate to INF if |failure_count_| is large,
// but multiplication operations for TimeDelta objects are saturated.
double backoff_factor = pow(2, failure_count_ - 1);
base::TimeDelta backoff_period = base_recovery_period_ * backoff_factor;
return backoff_period < refresh_period_ ? backoff_period : refresh_period_;
} else {
PA_LOG(ERROR) << "Error getting period for strategy: "
<< static_cast<int>(strategy_);
return base::TimeDelta();
}
}
} // namespace proximity_auth
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_CRYPTAUTH_SYNC_SCHEDULER_IMPL_H
#define COMPONENTS_PROXIMITY_AUTH_CRYPTAUTH_CRYPTAUTH_SYNC_SCHEDULER_IMPL_H
#include "base/timer/timer.h"
#include "components/proximity_auth/cryptauth/sync_scheduler.h"
namespace proximity_auth {
// Implementation of SyncScheduler.
class SyncSchedulerImpl : public SyncScheduler {
public:
// Creates the scheduler:
// |delegate|: Handles sync requests and must outlive the scheduler.
// |refresh_period|: The time to wait for the PERIODIC_REFRESH strategy.
// |base_recovery_period|: The initial time to wait for the
// AGGRESSIVE_RECOVERY strategy. The time delta is increased for each
// subsequent failure.
// |max_jitter_ratio|: The maximum ratio that the time to next sync can be
// jittered (both positively and negatively).
// |scheduler_name|: The name of the scheduler for debugging purposes.
SyncSchedulerImpl(Delegate* delegate,
base::TimeDelta refresh_period,
base::TimeDelta base_recovery_period,
double max_jitter_ratio,
const std::string& scheduler_name);
~SyncSchedulerImpl() override;
// SyncScheduler:
void Start(const base::TimeDelta& elapsed_time_since_last_sync,
Strategy strategy) override;
void ForceSync() override;
base::TimeDelta GetTimeToNextSync() const override;
Strategy GetStrategy() const override;
SyncState GetSyncState() const override;
protected:
// Creates and returns a base::Timer object. Exposed for testing.
virtual scoped_ptr<base::Timer> CreateTimer();
private:
// SyncScheduler:
void OnSyncCompleted(bool success) override;
// Called when |timer_| is fired.
void OnTimerFired();
// Schedules |timer_| for the next sync request.
void ScheduleNextSync(const base::TimeDelta& sync_delta);
// Adds a random jitter to the value of GetPeriod(). The returned
// TimeDelta will be clamped to be non-negative.
base::TimeDelta GetJitteredPeriod();
// Returns the time to wait for the current strategy.
base::TimeDelta GetPeriod();
// The delegate handling sync requests when they are fired.
Delegate* const delegate_;
// The time to wait until the next refresh when the last sync attempt was
// successful.
const base::TimeDelta refresh_period_;
// The base recovery period for the AGGRESSIVE_RECOVERY strategy before
// backoffs are applied.
const base::TimeDelta base_recovery_period_;
// The maximum percentage (both positively and negatively) that the time to
// wait between each sync request is jittered. The jitter is randomly applied
// to each period so we can avoid synchronous calls to the server.
const double max_jitter_ratio_;
// The name of the scheduler, used for debugging purposes.
const std::string scheduler_name_;
// The current strategy of the scheduler.
Strategy strategy_;
// The current state of the scheduler.
SyncState sync_state_;
// The number of failed syncs made in a row. Once a sync request succeeds,
// this counter is reset.
size_t failure_count_;
// Timer firing for the next sync request.
scoped_ptr<base::Timer> timer_;
base::WeakPtrFactory<SyncSchedulerImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SyncSchedulerImpl);
};
} // namespace proximity_auth
#endif // COMPONENTS_PROXIMITY_CRYPTAUTH_CRYPTAUTH_SYNC_SCHEDULER_IMPL_H
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment