blob: 80ce2085ff1727386528353df4a52404823e68b6 [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/model_association_manager.h"
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <functional>
#include <utility>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/stl_util.h"
#include "base/trace_event/trace_event.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/sync_stop_metadata_fate.h"
namespace syncer {
namespace {
static const ModelType kStartOrder[] = {
NIGORI, // Listed for completeness.
DEVICE_INFO,
PROXY_TABS, // Listed for completeness.
// Kick off the association of the non-UI types first so they can associate
// in parallel with the UI types.
PASSWORDS, AUTOFILL, AUTOFILL_PROFILE, AUTOFILL_WALLET_DATA,
AUTOFILL_WALLET_METADATA, AUTOFILL_WALLET_OFFER, EXTENSION_SETTINGS,
APP_SETTINGS, TYPED_URLS, HISTORY_DELETE_DIRECTIVES,
// Chrome OS settings affect the initial desktop appearance before the
// browser window opens, so start them before browser data types.
OS_PRIORITY_PREFERENCES, OS_PREFERENCES,
// UI thread data types.
BOOKMARKS, PREFERENCES, PRIORITY_PREFERENCES, EXTENSIONS, APPS, APP_LIST,
ARC_PACKAGE, READING_LIST, THEMES, SEARCH_ENGINES, SESSIONS, DICTIONARY,
DEPRECATED_FAVICON_IMAGES, DEPRECATED_FAVICON_TRACKING, PRINTERS,
USER_CONSENTS, USER_EVENTS, SHARING_MESSAGE, SUPERVISED_USER_SETTINGS,
SUPERVISED_USER_ALLOWLISTS, SEND_TAB_TO_SELF, SECURITY_EVENTS, WEB_APPS,
WIFI_CONFIGURATIONS};
static_assert(base::size(kStartOrder) ==
ModelType::NUM_ENTRIES - FIRST_REAL_MODEL_TYPE,
"When adding a new type, update kStartOrder.");
// The amount of time we wait for association to finish. If some types haven't
// finished association by the time, DataTypeManager is notified of the
// unfinished types.
const int64_t kAssociationTimeOutInSeconds = 600;
} // namespace
ModelAssociationManager::ModelAssociationManager(
const DataTypeController::TypeMap* controllers,
ModelAssociationManagerDelegate* processor)
: state_(IDLE),
controllers_(controllers),
delegate_(processor),
configure_status_(DataTypeManager::UNKNOWN),
notified_about_ready_for_configure_(false) {}
ModelAssociationManager::~ModelAssociationManager() = default;
void ModelAssociationManager::Initialize(ModelTypeSet desired_types,
ModelTypeSet preferred_types,
const ConfigureContext& context) {
// state_ can be INITIALIZED if types are reconfigured when
// data is being downloaded, so StartAssociationAsync() is never called for
// the first configuration.
DCHECK_NE(ASSOCIATING, state_);
// |desired_types| must be a subset of |preferred_types|.
DCHECK(preferred_types.HasAll(desired_types));
bool sync_mode_changed = configure_context_.sync_mode != context.sync_mode;
configure_context_ = context;
// Only keep types that have controllers.
desired_types_.Clear();
for (ModelType type : desired_types) {
auto dtc_iter = controllers_->find(type);
if (dtc_iter != controllers_->end()) {
DataTypeController* dtc = dtc_iter->second.get();
// Controllers in a FAILED state should have been filtered out by the
// DataTypeManager.
DCHECK_NE(dtc->state(), DataTypeController::FAILED);
desired_types_.Put(type);
}
}
DVLOG(1) << "ModelAssociationManager: Initializing for "
<< ModelTypeSetToString(desired_types_);
state_ = INITIALIZED;
notified_about_ready_for_configure_ = false;
DVLOG(1) << "ModelAssociationManager: Stopping disabled types.";
std::map<DataTypeController*, ShutdownReason> types_to_stop;
for (const auto& type_and_dtc : *controllers_) {
DataTypeController* dtc = type_and_dtc.second.get();
// We generally stop all data types which are not desired. When the storage
// option changes, we need to restart all data types so that they can
// re-wire to the correct storage.
bool should_stop = !desired_types_.Has(dtc->type()) || sync_mode_changed;
// If the datatype is already STOPPING, we also wait for it to stop, to make
// sure it's ready to start again (if appropriate).
if ((should_stop && dtc->state() != DataTypeController::NOT_RUNNING) ||
dtc->state() == DataTypeController::STOPPING) {
// Note: STOP_SYNC means we'll keep the Sync data around; DISABLE_SYNC
// means we'll clear it.
ShutdownReason reason =
preferred_types.Has(dtc->type()) ? STOP_SYNC : DISABLE_SYNC;
// If we're switching to transport-only mode, don't clear any old data.
// The reason is that if a user temporarily disables Sync, we don't want
// to wipe (and later redownload) all their data, just because Sync
// restarted in transport-only mode.
if (sync_mode_changed &&
configure_context_.sync_mode == SyncMode::kTransportOnly) {
reason = STOP_SYNC;
}
types_to_stop[dtc] = reason;
}
}
// Run LoadEnabledTypes() only after all relevant types are stopped.
// TODO(mastiz): Add test coverage to this waiting logic, including the
// case where the datatype is STOPPING when this function is called.
base::RepeatingClosure barrier_closure = base::BarrierClosure(
types_to_stop.size(),
base::BindOnce(&ModelAssociationManager::LoadEnabledTypes,
weak_ptr_factory_.GetWeakPtr()));
for (const auto& dtc_and_reason : types_to_stop) {
DataTypeController* dtc = dtc_and_reason.first;
const ShutdownReason reason = dtc_and_reason.second;
DVLOG(1) << "ModelAssociationManager: stop " << dtc->name() << " due to "
<< ShutdownReasonToString(reason);
StopDatatypeImpl(SyncError(), reason, dtc, barrier_closure);
}
}
void ModelAssociationManager::StopDatatype(ModelType type,
ShutdownReason shutdown_reason,
SyncError error) {
DCHECK(error.IsSet());
desired_types_.Remove(type);
DataTypeController* dtc = controllers_->find(type)->second.get();
if (dtc->state() != DataTypeController::NOT_RUNNING &&
dtc->state() != DataTypeController::STOPPING) {
StopDatatypeImpl(error, shutdown_reason, dtc, base::DoNothing());
}
// Removing a desired type may mean all models are now loaded.
NotifyDelegateIfReadyForConfigure();
}
void ModelAssociationManager::StopDatatypeImpl(
const SyncError& error,
ShutdownReason shutdown_reason,
DataTypeController* dtc,
DataTypeController::StopCallback callback) {
loaded_types_.Remove(dtc->type());
associated_types_.Remove(dtc->type());
associating_types_.Remove(dtc->type());
DCHECK(error.IsSet() || (dtc->state() != DataTypeController::NOT_RUNNING));
delegate_->OnSingleDataTypeWillStop(dtc->type(), error);
// Note: Depending on |shutdown_reason|, USS types might clear their metadata
// in response to Stop().
dtc->Stop(shutdown_reason, std::move(callback));
}
void ModelAssociationManager::LoadEnabledTypes() {
// Load in kStartOrder.
for (ModelType type : kStartOrder) {
if (!desired_types_.Has(type))
continue;
auto dtc_iter = controllers_->find(type);
DCHECK(dtc_iter != controllers_->end());
DataTypeController* dtc = dtc_iter->second.get();
DCHECK_NE(DataTypeController::STOPPING, dtc->state());
if (dtc->state() == DataTypeController::NOT_RUNNING) {
DCHECK(!loaded_types_.Has(dtc->type()));
DCHECK(!associated_types_.Has(dtc->type()));
dtc->LoadModels(
configure_context_,
base::BindRepeating(&ModelAssociationManager::ModelLoadCallback,
weak_ptr_factory_.GetWeakPtr()));
}
}
// It's possible that all models are already loaded.
NotifyDelegateIfReadyForConfigure();
}
void ModelAssociationManager::StartAssociationAsync(
const ModelTypeSet& types_to_associate) {
DCHECK_EQ(INITIALIZED, state_);
DVLOG(1) << "Starting association for "
<< ModelTypeSetToString(types_to_associate);
state_ = ASSOCIATING;
requested_types_ = types_to_associate;
associating_types_ = types_to_associate;
associating_types_.RetainAll(desired_types_);
associating_types_.RemoveAll(associated_types_);
// Assume success.
configure_status_ = DataTypeManager::OK;
// Done if no types to associate.
if (associating_types_.Empty()) {
ModelAssociationDone(INITIALIZED);
return;
}
timer_.Start(FROM_HERE,
base::TimeDelta::FromSeconds(kAssociationTimeOutInSeconds),
base::BindOnce(&ModelAssociationManager::ModelAssociationDone,
weak_ptr_factory_.GetWeakPtr(), INITIALIZED));
// Associate types that are already loaded in specified order.
for (ModelType type : kStartOrder) {
if (!associating_types_.Has(type) || !loaded_types_.Has(type))
continue;
DataTypeController* dtc = controllers_->find(type)->second.get();
TRACE_EVENT_ASYNC_BEGIN1("sync", "ModelAssociation", dtc, "DataType",
ModelTypeToString(type));
MarkDataTypeAssociationDone(type);
}
}
void ModelAssociationManager::Stop(ShutdownReason shutdown_reason) {
// Ignore callbacks from controllers.
weak_ptr_factory_.InvalidateWeakPtrs();
// Stop started data types.
for (const auto& type_and_dtc : *controllers_) {
DataTypeController* dtc = type_and_dtc.second.get();
if (dtc->state() != DataTypeController::NOT_RUNNING &&
dtc->state() != DataTypeController::STOPPING) {
// We don't really wait until all datatypes have been fully stopped, which
// is only required (and in fact waited for) when Initialize() is called.
StopDatatypeImpl(SyncError(), shutdown_reason, dtc, base::DoNothing());
DVLOG(1) << "ModelAssociationManager: Stopped " << dtc->name();
}
}
desired_types_.Clear();
loaded_types_.Clear();
associated_types_.Clear();
if (state_ == ASSOCIATING) {
if (configure_status_ == DataTypeManager::OK)
configure_status_ = DataTypeManager::ABORTED;
DVLOG(1) << "ModelAssociationManager: Calling OnModelAssociationDone";
ModelAssociationDone(IDLE);
} else {
DCHECK(associating_types_.Empty());
DCHECK(requested_types_.Empty());
state_ = IDLE;
}
}
void ModelAssociationManager::ModelLoadCallback(ModelType type,
const SyncError& error) {
DVLOG(1) << "ModelAssociationManager: ModelLoadCallback for "
<< ModelTypeToString(type);
if (error.IsSet()) {
DVLOG(1) << "ModelAssociationManager: Type encountered an error.";
desired_types_.Remove(type);
DataTypeController* dtc = controllers_->find(type)->second.get();
StopDatatypeImpl(error, STOP_SYNC, dtc, base::DoNothing());
NotifyDelegateIfReadyForConfigure();
return;
}
// This happens when slow loading type is disabled by new configuration or
// the model came unready during loading.
if (!desired_types_.Has(type))
return;
DCHECK(!loaded_types_.Has(type));
loaded_types_.Put(type);
NotifyDelegateIfReadyForConfigure();
if (associating_types_.Has(type)) {
DataTypeController* dtc = controllers_->find(type)->second.get();
// If initial sync was done for this datatype then
// NotifyDelegateIfReadyForConfigure possibly already triggered model
// association and StartAssociating was already called for this type. To
// ensure StartAssociating is called only once only make a call if state is
// MODEL_LOADED.
// TODO(pavely): Add test for this scenario in DataTypeManagerImpl
// unittests.
// TODO(crbug.com/647505): The above sounds quite broken (will
// MarkDataTypeAssociationDone never get called in that case?!), and also
// outdated (StartAssociating doesn't exist anymore). Can we just move the
// NotifyDelegateIfReadyForConfigure call below?
if (dtc->state() == DataTypeController::MODEL_LOADED) {
MarkDataTypeAssociationDone(type);
}
}
}
void ModelAssociationManager::MarkDataTypeAssociationDone(ModelType type) {
DCHECK(desired_types_.Has(type));
DCHECK(associating_types_.Has(type));
DCHECK(loaded_types_.Has(type));
DCHECK(!associated_types_.Has(type));
associated_types_.Put(type);
// TODO(crbug.com/647505): Should we check this *before* adding the type to
// |associated_types_|?
if (state_ != ASSOCIATING)
return;
TRACE_EVENT_ASYNC_END1("sync", "ModelAssociation",
controllers_->find(type)->second.get(), "DataType",
ModelTypeToString(type));
// Track the merge results if we succeeded or an association failure
// occurred.
if (ProtocolTypes().Has(type))
delegate_->OnSingleDataTypeAssociationDone(type);
associating_types_.Remove(type);
if (associating_types_.Empty())
ModelAssociationDone(INITIALIZED);
}
void ModelAssociationManager::ModelAssociationDone(State new_state) {
DCHECK_NE(IDLE, state_);
if (state_ == INITIALIZED) {
// No associations are currently happening. Just reset the state.
state_ = new_state;
return;
}
DVLOG(1) << "Model association complete for "
<< ModelTypeSetToString(requested_types_);
timer_.Stop();
// Treat any unfinished types as having errors.
desired_types_.RemoveAll(associating_types_);
for (const auto& type_and_dtc : *controllers_) {
DataTypeController* dtc = type_and_dtc.second.get();
if (associating_types_.Has(dtc->type()) &&
dtc->state() != DataTypeController::NOT_RUNNING &&
dtc->state() != DataTypeController::STOPPING) {
base::UmaHistogramEnumeration("Sync.ConfigureFailed",
ModelTypeHistogramValue(dtc->type()));
StopDatatypeImpl(SyncError(FROM_HERE, SyncError::DATATYPE_ERROR,
"Association timed out.", dtc->type()),
STOP_SYNC, dtc, base::DoNothing());
}
}
DataTypeManager::ConfigureResult result(configure_status_, requested_types_);
// Need to reset state before invoking delegate in order to avoid re-entrancy
// issues (delegate may trigger a reconfiguration).
associating_types_.Clear();
requested_types_.Clear();
state_ = new_state;
delegate_->OnModelAssociationDone(result);
}
base::OneShotTimer* ModelAssociationManager::GetTimerForTesting() {
return &timer_;
}
void ModelAssociationManager::NotifyDelegateIfReadyForConfigure() {
if (notified_about_ready_for_configure_)
return;
for (ModelType type : desired_types_) {
if (!loaded_types_.Has(type)) {
// At least one type is not ready.
return;
}
}
notified_about_ready_for_configure_ = true;
delegate_->OnAllDataTypesReadyForConfigure();
}
} // namespace syncer