blob: d3fc3becd7bedd712b85b84804b132a4a8051174 [file] [log] [blame]
// Copyright 2013 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 "chrome/browser/sync_file_system/sync_process_runner.h"
#include <utility>
#include "base/bind.h"
#include "base/format_macros.h"
#include "base/macros.h"
#include "chrome/browser/sync_file_system/logger.h"
namespace sync_file_system {
const int64_t SyncProcessRunner::kSyncDelayInMilliseconds =
1 * base::Time::kMillisecondsPerSecond; // 1 sec
const int64_t SyncProcessRunner::kSyncDelayWithSyncError =
3 * base::Time::kMillisecondsPerSecond; // 3 sec
const int64_t SyncProcessRunner::kSyncDelayFastInMilliseconds = 100; // 100 ms
const int SyncProcessRunner::kPendingChangeThresholdForFastSync = 10;
const int64_t SyncProcessRunner::kSyncDelaySlowInMilliseconds =
30 * base::Time::kMillisecondsPerSecond; // 30 sec
const int64_t SyncProcessRunner::kSyncDelayMaxInMilliseconds =
30 * 60 * base::Time::kMillisecondsPerSecond; // 30 min
namespace {
class BaseTimerHelper : public SyncProcessRunner::TimerHelper {
public:
BaseTimerHelper() {}
bool IsRunning() override { return timer_.IsRunning(); }
void Start(const base::Location& from_here,
const base::TimeDelta& delay,
const base::Closure& closure) override {
timer_.Start(from_here, delay, closure);
}
base::TimeTicks Now() const override { return base::TimeTicks::Now(); }
~BaseTimerHelper() override {}
private:
base::OneShotTimer timer_;
DISALLOW_COPY_AND_ASSIGN(BaseTimerHelper);
};
bool WasSuccessfulSync(SyncStatusCode status) {
return status == SYNC_STATUS_OK ||
status == SYNC_STATUS_HAS_CONFLICT ||
status == SYNC_STATUS_NO_CONFLICT ||
status == SYNC_STATUS_NO_CHANGE_TO_SYNC ||
status == SYNC_STATUS_UNKNOWN_ORIGIN ||
status == SYNC_STATUS_RETRY;
}
} // namespace
SyncProcessRunner::SyncProcessRunner(const std::string& name,
Client* client,
std::unique_ptr<TimerHelper> timer_helper,
size_t max_parallel_task)
: name_(name),
client_(client),
max_parallel_task_(max_parallel_task),
running_tasks_(0),
timer_helper_(std::move(timer_helper)),
service_state_(SYNC_SERVICE_RUNNING),
pending_changes_(0),
factory_(this) {
DCHECK_LE(1u, max_parallel_task_);
if (!timer_helper_)
timer_helper_.reset(new BaseTimerHelper);
}
SyncProcessRunner::~SyncProcessRunner() {}
void SyncProcessRunner::Schedule() {
if (pending_changes_ == 0) {
ScheduleInternal(kSyncDelayMaxInMilliseconds);
return;
}
SyncServiceState last_service_state = service_state_;
service_state_ = GetServiceState();
switch (service_state_) {
case SYNC_SERVICE_RUNNING:
ResetThrottling();
if (pending_changes_ > kPendingChangeThresholdForFastSync)
ScheduleInternal(kSyncDelayFastInMilliseconds);
else
ScheduleInternal(kSyncDelayInMilliseconds);
return;
case SYNC_SERVICE_TEMPORARY_UNAVAILABLE:
if (last_service_state != service_state_)
ThrottleSync(kSyncDelaySlowInMilliseconds);
ScheduleInternal(kSyncDelaySlowInMilliseconds);
return;
case SYNC_SERVICE_AUTHENTICATION_REQUIRED:
case SYNC_SERVICE_DISABLED:
if (last_service_state != service_state_)
ThrottleSync(kSyncDelaySlowInMilliseconds);
ScheduleInternal(kSyncDelayMaxInMilliseconds);
return;
}
NOTREACHED();
ScheduleInternal(kSyncDelayMaxInMilliseconds);
}
void SyncProcessRunner::ThrottleSync(int64_t base_delay) {
base::TimeTicks now = timer_helper_->Now();
base::TimeDelta elapsed = std::min(now, throttle_until_) - throttle_from_;
DCHECK(base::TimeDelta() <= elapsed);
throttle_from_ = now;
// Extend throttling duration by twice the elapsed time.
// That is, if the backoff repeats in a short period, the throttling period
// doesn't grow exponentially. If the backoff happens on the end of
// throttling period, it causes another throttling period that is twice as
// long as previous.
base::TimeDelta base_delay_delta =
base::TimeDelta::FromMilliseconds(base_delay);
const base::TimeDelta max_delay =
base::TimeDelta::FromMilliseconds(kSyncDelayMaxInMilliseconds);
throttle_until_ =
std::min(now + max_delay,
std::max(now + base_delay_delta, throttle_until_ + 2 * elapsed));
}
void SyncProcessRunner::ResetOldThrottling() {
if (throttle_until_ < base::TimeTicks::Now())
ResetThrottling();
}
void SyncProcessRunner::ResetThrottling() {
throttle_from_ = base::TimeTicks();
throttle_until_ = base::TimeTicks();
}
SyncServiceState SyncProcessRunner::GetServiceState() {
return client_->GetSyncServiceState();
}
void SyncProcessRunner::OnChangesUpdated(int64_t pending_changes) {
DCHECK_GE(pending_changes, 0);
int64_t old_pending_changes = pending_changes_;
pending_changes_ = pending_changes;
if (old_pending_changes != pending_changes) {
CheckIfIdle();
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"[%s] pending_changes updated: %" PRId64,
name_.c_str(), pending_changes);
}
Schedule();
}
SyncFileSystemService* SyncProcessRunner::GetSyncService() {
return client_->GetSyncService();
}
void SyncProcessRunner::Finished(const base::TimeTicks& start_time,
SyncStatusCode status) {
DCHECK_LT(0u, running_tasks_);
DCHECK_LE(running_tasks_, max_parallel_task_);
--running_tasks_;
CheckIfIdle();
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"[%s] * Finished (elapsed: %" PRId64 " ms)", name_.c_str(),
(timer_helper_->Now() - start_time).InMilliseconds());
if (status == SYNC_STATUS_NO_CHANGE_TO_SYNC ||
status == SYNC_STATUS_FILE_BUSY) {
ScheduleInternal(kSyncDelayMaxInMilliseconds);
return;
}
if (WasSuccessfulSync(status))
ResetOldThrottling();
else
ThrottleSync(kSyncDelayWithSyncError);
Schedule();
}
void SyncProcessRunner::Run() {
if (running_tasks_ >= max_parallel_task_)
return;
++running_tasks_;
base::TimeTicks now = timer_helper_->Now();
last_run_ = now;
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"[%s] * Started", name_.c_str());
StartSync(base::Bind(&SyncProcessRunner::Finished, factory_.GetWeakPtr(),
now));
if (running_tasks_ < max_parallel_task_)
Schedule();
}
void SyncProcessRunner::ScheduleInternal(int64_t delay) {
base::TimeTicks now = timer_helper_->Now();
base::TimeTicks next_scheduled;
if (timer_helper_->IsRunning()) {
next_scheduled = last_run_ + base::TimeDelta::FromMilliseconds(delay);
if (next_scheduled < now) {
next_scheduled =
now + base::TimeDelta::FromMilliseconds(kSyncDelayFastInMilliseconds);
}
} else {
next_scheduled = now + base::TimeDelta::FromMilliseconds(delay);
}
if (next_scheduled < throttle_until_)
next_scheduled = throttle_until_;
if (timer_helper_->IsRunning() && last_scheduled_ == next_scheduled)
return;
util::Log(logging::LOG_VERBOSE, FROM_HERE,
"[%s] Scheduling task in %" PRId64 " ms",
name_.c_str(), (next_scheduled - now).InMilliseconds());
last_scheduled_ = next_scheduled;
timer_helper_->Start(
FROM_HERE, next_scheduled - now,
base::Bind(&SyncProcessRunner::Run, base::Unretained(this)));
}
void SyncProcessRunner::CheckIfIdle() {
if (pending_changes_ == 0 && running_tasks_ == 0)
client_->OnSyncIdle();
}
} // namespace sync_file_system