| OLD | NEW |
| (Empty) |
| 1 // 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 | |
| 5 #include "components/proximity_auth/cryptauth/sync_scheduler_impl.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <cmath> | |
| 9 #include <limits> | |
| 10 | |
| 11 #include "base/bind.h" | |
| 12 #include "base/memory/ptr_util.h" | |
| 13 #include "base/numerics/safe_conversions.h" | |
| 14 #include "base/rand_util.h" | |
| 15 #include "base/strings/stringprintf.h" | |
| 16 #include "components/proximity_auth/logging/logging.h" | |
| 17 | |
| 18 namespace proximity_auth { | |
| 19 | |
| 20 namespace { | |
| 21 | |
| 22 // Returns a human readable string given a |time_delta|. | |
| 23 std::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 | |
| 39 SyncSchedulerImpl::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 | |
| 55 SyncSchedulerImpl::~SyncSchedulerImpl() { | |
| 56 } | |
| 57 | |
| 58 void 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; | |
| 74 | |
| 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; | |
| 80 | |
| 81 ScheduleNextSync(sync_delta); | |
| 82 } | |
| 83 | |
| 84 void SyncSchedulerImpl::ForceSync() { | |
| 85 OnTimerFired(); | |
| 86 } | |
| 87 | |
| 88 base::TimeDelta SyncSchedulerImpl::GetTimeToNextSync() const { | |
| 89 if (!timer_) | |
| 90 return base::TimeDelta::FromSeconds(0); | |
| 91 return timer_->GetCurrentDelay(); | |
| 92 } | |
| 93 | |
| 94 SyncScheduler::Strategy SyncSchedulerImpl::GetStrategy() const { | |
| 95 return strategy_; | |
| 96 } | |
| 97 | |
| 98 SyncScheduler::SyncState SyncSchedulerImpl::GetSyncState() const { | |
| 99 return sync_state_; | |
| 100 } | |
| 101 | |
| 102 void 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( | |
| 116 base::MakeUnique<SyncRequest>(weak_ptr_factory_.GetWeakPtr())); | |
| 117 } | |
| 118 | |
| 119 std::unique_ptr<base::Timer> SyncSchedulerImpl::CreateTimer() { | |
| 120 bool retain_user_task = false; | |
| 121 bool is_repeating = false; | |
| 122 return base::MakeUnique<base::Timer>(retain_user_task, is_repeating); | |
| 123 } | |
| 124 | |
| 125 void 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 | |
| 150 void 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 | |
| 170 base::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 | |
| 179 base::TimeDelta SyncSchedulerImpl::GetPeriod() { | |
| 180 if (strategy_ == Strategy::PERIODIC_REFRESH) { | |
| 181 return refresh_period_; | |
| 182 } else if (strategy_ == Strategy::AGGRESSIVE_RECOVERY && failure_count_ > 0) { | |
| 183 // 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_; | |
| 190 } else { | |
| 191 PA_LOG(ERROR) << "Error getting period for strategy: " | |
| 192 << static_cast<int>(strategy_); | |
| 193 return base::TimeDelta(); | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 } // namespace proximity_auth | |
| OLD | NEW |