blob: 2fdd9d700c0d031e59d1010c5e269edddfb2fdac [file] [log] [blame]
tengsa2a7afa42015-05-21 04:38:081// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
khorimoto999e934c2016-11-18 20:10:425#include "components/cryptauth/sync_scheduler_impl.h"
tengsa2a7afa42015-05-21 04:38:086
7#include <algorithm>
8#include <cmath>
9#include <limits>
Gyuyoung Kim6afb5082018-01-19 13:35:5710#include <memory>
tengsa2a7afa42015-05-21 04:38:0811
12#include "base/bind.h"
13#include "base/numerics/safe_conversions.h"
14#include "base/rand_util.h"
15#include "base/strings/stringprintf.h"
James Hawkins813085e2018-03-30 18:56:4116#include "chromeos/components/proximity_auth/logging/logging.h"
tengsa2a7afa42015-05-21 04:38:0817
khorimoto999e934c2016-11-18 20:10:4218namespace cryptauth {
tengsa2a7afa42015-05-21 04:38:0819
20namespace {
21
22// Returns a human readable string given a |time_delta|.
23std::string TimeDeltaToString(const base::TimeDelta& time_delta) {
24 if (time_delta.InDays() > 0)
25 return base::StringPrintf("%d days", time_delta.InDays());
26
27 if (time_delta.InHours() > 0)
28 return base::StringPrintf("%d hours", time_delta.InHours());
29
30 if (time_delta.InMinutes() > 0)
31 return base::StringPrintf("%d minutes", time_delta.InMinutes());
32
33 return base::StringPrintf("%d seconds",
34 base::saturated_cast<int>(time_delta.InSeconds()));
35}
36
37} // namespace
38
39SyncSchedulerImpl::SyncSchedulerImpl(Delegate* delegate,
40 base::TimeDelta refresh_period,
41 base::TimeDelta base_recovery_period,
42 double max_jitter_ratio,
43 const std::string& scheduler_name)
44 : delegate_(delegate),
45 refresh_period_(refresh_period),
46 base_recovery_period_(base_recovery_period),
47 max_jitter_ratio_(max_jitter_ratio),
48 scheduler_name_(scheduler_name),
49 strategy_(Strategy::PERIODIC_REFRESH),
50 sync_state_(SyncState::NOT_STARTED),
51 failure_count_(0),
52 weak_ptr_factory_(this) {
53}
54
55SyncSchedulerImpl::~SyncSchedulerImpl() {
56}
57
58void SyncSchedulerImpl::Start(
59 const base::TimeDelta& elapsed_time_since_last_sync,
60 Strategy strategy) {
61 strategy_ = strategy;
62 sync_state_ = SyncState::WAITING_FOR_REFRESH;
63 // We reset the failure backoff when the scheduler is started again, as the
64 // configuration that caused the previous attempts to fail most likely won't
65 // be present after a restart.
66 if (strategy_ == Strategy::AGGRESSIVE_RECOVERY)
67 failure_count_ = 1;
68
69 // To take into account the time waited when the system is powered off, we
70 // subtract the time elapsed with a normal sync period to the initial time
71 // to wait.
72 base::TimeDelta sync_delta =
73 GetJitteredPeriod() - elapsed_time_since_last_sync;
tengsf3298ad2015-06-02 17:13:2074
75 // The elapsed time may be negative if the system clock is changed. In this
76 // case, we immediately schedule a sync.
77 base::TimeDelta zero_delta = base::TimeDelta::FromSeconds(0);
78 if (elapsed_time_since_last_sync < zero_delta || sync_delta < zero_delta)
79 sync_delta = zero_delta;
tengsa2a7afa42015-05-21 04:38:0880
81 ScheduleNextSync(sync_delta);
82}
83
84void SyncSchedulerImpl::ForceSync() {
85 OnTimerFired();
86}
87
88base::TimeDelta SyncSchedulerImpl::GetTimeToNextSync() const {
89 if (!timer_)
90 return base::TimeDelta::FromSeconds(0);
91 return timer_->GetCurrentDelay();
92}
93
94SyncScheduler::Strategy SyncSchedulerImpl::GetStrategy() const {
95 return strategy_;
96}
97
98SyncScheduler::SyncState SyncSchedulerImpl::GetSyncState() const {
99 return sync_state_;
100}
101
102void SyncSchedulerImpl::OnTimerFired() {
103 timer_.reset();
104 if (strategy_ == Strategy::PERIODIC_REFRESH) {
105 PA_LOG(INFO) << "Timer fired for periodic refresh, making request...";
106 sync_state_ = SyncState::SYNC_IN_PROGRESS;
107 } else if (strategy_ == Strategy::AGGRESSIVE_RECOVERY) {
108 PA_LOG(INFO) << "Timer fired for aggressive recovery, making request...";
109 sync_state_ = SyncState::SYNC_IN_PROGRESS;
110 } else {
111 NOTREACHED();
112 return;
113 }
114
115 delegate_->OnSyncRequested(
Gyuyoung Kim6afb5082018-01-19 13:35:57116 std::make_unique<SyncRequest>(weak_ptr_factory_.GetWeakPtr()));
tengsa2a7afa42015-05-21 04:38:08117}
118
dcheng2f012692016-04-21 00:19:34119std::unique_ptr<base::Timer> SyncSchedulerImpl::CreateTimer() {
tengsa2a7afa42015-05-21 04:38:08120 bool retain_user_task = false;
121 bool is_repeating = false;
Gyuyoung Kim6afb5082018-01-19 13:35:57122 return std::make_unique<base::Timer>(retain_user_task, is_repeating);
tengsa2a7afa42015-05-21 04:38:08123}
124
125void SyncSchedulerImpl::ScheduleNextSync(const base::TimeDelta& sync_delta) {
126 if (sync_state_ != SyncState::WAITING_FOR_REFRESH) {
127 PA_LOG(ERROR) << "Unexpected state when scheduling next sync: sync_state="
128 << static_cast<int>(sync_state_);
129 return;
130 }
131
132 bool is_aggressive_recovery = (strategy_ == Strategy::AGGRESSIVE_RECOVERY);
133 PA_LOG(INFO) << "Scheduling next sync for " << scheduler_name_ << ":\n"
134 << " Strategy: " << (is_aggressive_recovery
135 ? "Aggressive Recovery"
136 : "Periodic Refresh") << "\n"
137 << " Time Delta: " << TimeDeltaToString(sync_delta)
138 << (is_aggressive_recovery
139 ? base::StringPrintf(
140 "\n Previous Failures: %d",
141 base::saturated_cast<int>(failure_count_))
142 : "");
143
144 timer_ = CreateTimer();
145 timer_->Start(FROM_HERE, sync_delta,
146 base::Bind(&SyncSchedulerImpl::OnTimerFired,
147 weak_ptr_factory_.GetWeakPtr()));
148}
149
150void SyncSchedulerImpl::OnSyncCompleted(bool success) {
151 if (sync_state_ != SyncState::SYNC_IN_PROGRESS) {
152 PA_LOG(ERROR) << "Unexpected state when sync completed: sync_state="
153 << static_cast<int>(sync_state_)
154 << ", strategy_=" << static_cast<int>(strategy_);
155 return;
156 }
157 sync_state_ = SyncState::WAITING_FOR_REFRESH;
158
159 if (success) {
160 strategy_ = Strategy::PERIODIC_REFRESH;
161 failure_count_ = 0;
162 } else {
163 strategy_ = Strategy::AGGRESSIVE_RECOVERY;
164 ++failure_count_;
165 }
166
167 ScheduleNextSync(GetJitteredPeriod());
168}
169
170base::TimeDelta SyncSchedulerImpl::GetJitteredPeriod() {
171 double jitter = 2 * max_jitter_ratio_ * (base::RandDouble() - 0.5);
172 base::TimeDelta period = GetPeriod();
173 base::TimeDelta jittered_time_delta = period + (period * jitter);
174 if (jittered_time_delta.InMilliseconds() < 0)
175 jittered_time_delta = base::TimeDelta::FromMilliseconds(0);
176 return jittered_time_delta;
177}
178
179base::TimeDelta SyncSchedulerImpl::GetPeriod() {
Lei Zhang7cfb36e2017-11-28 01:41:56180 if (strategy_ == Strategy::PERIODIC_REFRESH)
tengsa2a7afa42015-05-21 04:38:08181 return refresh_period_;
Lei Zhang7cfb36e2017-11-28 01:41:56182 if (strategy_ == Strategy::AGGRESSIVE_RECOVERY && failure_count_ > 0) {
tengsa2a7afa42015-05-21 04:38:08183 // The backoff for each consecutive failure is exponentially doubled until
184 // it is equal to the normal refresh period.
185 // Note: |backoff_factor| may evaulate to INF if |failure_count_| is large,
186 // but multiplication operations for TimeDelta objects are saturated.
187 double backoff_factor = pow(2, failure_count_ - 1);
188 base::TimeDelta backoff_period = base_recovery_period_ * backoff_factor;
189 return backoff_period < refresh_period_ ? backoff_period : refresh_period_;
tengsa2a7afa42015-05-21 04:38:08190 }
Lei Zhang7cfb36e2017-11-28 01:41:56191 PA_LOG(ERROR) << "Error getting period for strategy: "
192 << static_cast<int>(strategy_);
193 return base::TimeDelta();
tengsa2a7afa42015-05-21 04:38:08194}
195
khorimoto999e934c2016-11-18 20:10:42196} // namespace cryptauth