blob: 43a6b5769cac2fc059131a9f7bfb8b4f3b610924 [file] [log] [blame]
// Copyright 2014 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/sync/driver/startup_controller.h"
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/location.h"
#include "base/metrics/histogram_functions.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_number_conversions.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "components/sync/driver/sync_driver_switches.h"
namespace syncer {
namespace {
// The amount of time we'll wait to initialize sync if no data type requests
// immediately initialization.
constexpr base::TimeDelta kDefaultDeferredInitDelay = base::Seconds(10);
base::TimeDelta GetDeferredInitDelay() {
const base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess();
if (cmdline->HasSwitch(switches::kSyncDeferredStartupTimeoutSeconds)) {
int timeout = 0;
if (base::StringToInt(cmdline->GetSwitchValueASCII(
switches::kSyncDeferredStartupTimeoutSeconds),
&timeout)) {
DCHECK_GE(timeout, 0);
DVLOG(2) << "Sync StartupController overriding startup timeout to "
<< timeout << " seconds.";
return base::Seconds(timeout);
}
}
return kDefaultDeferredInitDelay;
}
bool IsDeferredStartupEnabled() {
return !base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kSyncDisableDeferredStartup);
}
} // namespace
StartupController::StartupController(
base::RepeatingCallback<ModelTypeSet()> get_preferred_data_types,
base::RepeatingCallback<bool()> should_start,
base::RepeatingClosure start_engine,
policy::PolicyService* policy_service)
: get_preferred_data_types_callback_(std::move(get_preferred_data_types)),
should_start_callback_(std::move(should_start)),
start_engine_callback_(std::move(start_engine)),
bypass_deferred_startup_(false),
policy_service_(policy_service) {
if (policy_service_ && policy_service_->IsFirstPolicyLoadComplete(
policy::PolicyDomain::POLICY_DOMAIN_CHROME)) {
// Policies are already loaded; no need to track the policy service.
policy_service_ = nullptr;
} else if (policy_service_) {
policy_service_->AddObserver(policy::PolicyDomain::POLICY_DOMAIN_CHROME,
this);
}
}
StartupController::~StartupController() {
if (policy_service_) {
policy_service_->RemoveObserver(policy::PolicyDomain::POLICY_DOMAIN_CHROME,
this);
}
}
void StartupController::Reset() {
bypass_deferred_startup_ = false;
start_up_time_ = base::Time();
start_engine_time_ = base::Time();
// Don't let previous timers affect us post-reset.
weak_factory_.InvalidateWeakPtrs();
}
void StartupController::StartUp(StartUpDeferredOption deferred_option) {
const bool first_start = start_up_time_.is_null();
if (first_start) {
start_up_time_ = base::Time::Now();
}
if (deferred_option == STARTUP_DEFERRED && IsDeferredStartupEnabled() &&
get_preferred_data_types_callback_.Run().Has(SESSIONS)) {
if (first_start) {
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&StartupController::OnFallbackStartupTimerExpired,
weak_factory_.GetWeakPtr()),
GetDeferredInitDelay());
}
return;
}
if (start_engine_time_.is_null()) {
start_engine_time_ = base::Time::Now();
start_engine_callback_.Run();
}
}
void StartupController::TryStart(bool force_immediate) {
// Post a task instead of running the startup checks directly, to guarantee
// that |start_engine_callback_| is never called synchronously from
// TryStart().
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&StartupController::TryStartImpl,
weak_factory_.GetWeakPtr(), force_immediate));
}
void StartupController::TryStartImpl(bool force_immediate) {
// Try starting up the sync engine if all policies are ready, otherwise wait
// at most |switches::kSyncPolicyLoadTimeout|.
if (!ArePoliciesReady()) {
if (waiting_for_policies_start_time_.is_null()) {
waiting_for_policies_start_time_ = base::Time::Now();
wait_for_policy_timer_.Start(
FROM_HERE, switches::kSyncPolicyLoadTimeout.Get(),
base::BindOnce(&StartupController::OnFirstPoliciesLoadedTimeout,
base::Unretained(this)));
}
// If the Service had to start immediately, bypass the deferred startup when
// we receive the policies.
if (force_immediate) {
bypass_deferred_startup_ = true;
}
return;
}
if (!should_start_callback_.Run()) {
return;
}
// For performance reasons, defer the heavy lifting for sync init unless:
//
// - a datatype has requested an immediate start of sync, or
// - sync needs to start up the engine immediately to provide control state
// and encryption information to the UI.
StartUp((force_immediate || bypass_deferred_startup_) ? STARTUP_IMMEDIATE
: STARTUP_DEFERRED);
}
void StartupController::RecordTimeDeferred(DeferredInitTrigger trigger) {
DCHECK(!start_up_time_.is_null());
base::TimeDelta time_deferred = base::Time::Now() - start_up_time_;
base::UmaHistogramCustomTimes("Sync.Startup.TimeDeferred2", time_deferred,
base::Seconds(0), base::Minutes(2), 60);
base::UmaHistogramEnumeration("Sync.Startup.DeferredInitTrigger", trigger);
}
void StartupController::OnFallbackStartupTimerExpired() {
DCHECK(IsDeferredStartupEnabled());
if (!start_engine_time_.is_null()) {
return;
}
DVLOG(2) << "Sync deferred init fallback timer expired, starting engine.";
RecordTimeDeferred(DeferredInitTrigger::kFallbackTimer);
// Once the deferred init timer has expired, don't defer startup again (until
// Reset() or browser restart), even if this startup attempt doesn't succeed.
bypass_deferred_startup_ = true;
TryStart(/*force_immediate=*/false);
}
StartupController::State StartupController::GetState() const {
if (!start_engine_time_.is_null()) {
return State::STARTED;
}
if (!ArePoliciesReady() && !waiting_for_policies_start_time_.is_null()) {
return State::STARTING_DEFERRED;
}
if (!start_up_time_.is_null()) {
return State::STARTING_DEFERRED;
}
return State::NOT_STARTED;
}
void StartupController::OnFirstPoliciesLoaded(policy::PolicyDomain domain) {
DCHECK_EQ(domain, policy::PolicyDomain::POLICY_DOMAIN_CHROME);
// Cancel the timeout timer.
wait_for_policy_timer_.AbandonAndStop();
OnFirstPoliciesLoadedImpl(/*timeout=*/false);
}
void StartupController::OnFirstPoliciesLoadedTimeout() {
OnFirstPoliciesLoadedImpl(/*timeout=*/true);
}
bool StartupController::ArePoliciesReady() const {
// |policy_service_| is non-null iff we're waiting for policies to load.
return policy_service_ == nullptr;
}
void StartupController::TriggerPolicyWaitTimeoutForTest() {
OnFirstPoliciesLoadedTimeout();
}
void StartupController::OnFirstPoliciesLoadedImpl(bool timeout) {
policy_service_->RemoveObserver(policy::PolicyDomain::POLICY_DOMAIN_CHROME,
this);
policy_service_ = nullptr;
base::UmaHistogramBoolean("Sync.Startup.PolicyLoadTimeout2", timeout);
base::UmaHistogramTimes(
"Sync.Startup.PolicyLoadStartupDelay",
waiting_for_policies_start_time_.is_null()
? base::TimeDelta()
: base::Time::Now() - waiting_for_policies_start_time_);
// Only try to start the engine if we explicitly tried to start but had to
// wait for policies to be loaded.
if (!waiting_for_policies_start_time_.is_null()) {
TryStart(/*force_immediate=*/false);
}
}
void StartupController::OnDataTypeRequestsSyncStartup(ModelType type) {
if (!IsDeferredStartupEnabled()) {
DVLOG(2) << "Ignoring data type request for sync startup: "
<< ModelTypeToString(type);
return;
}
if (!start_engine_time_.is_null()) {
return;
}
DVLOG(2) << "Data type requesting sync startup: " << ModelTypeToString(type);
if (!start_up_time_.is_null()) {
RecordTimeDeferred(DeferredInitTrigger::kDataTypeRequest);
}
bypass_deferred_startup_ = true;
TryStart(/*force_immediate=*/false);
}
} // namespace syncer