blob: 6e19c732b49c910823fd21c606bbfd6af1223238 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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_load_manager.h"
#include <map>
#include <utility>
#include "base/barrier_closure.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/timer/elapsed_timer.h"
#include "components/sync/base/features.h"
#include "components/sync/base/model_type.h"
#include "components/sync/base/sync_stop_metadata_fate.h"
#include "components/sync/model/sync_error.h"
namespace syncer {
ModelLoadManager::ModelLoadManager(
const DataTypeController::TypeMap* controllers,
ModelLoadManagerDelegate* processor)
: controllers_(controllers), delegate_(processor) {}
ModelLoadManager::~ModelLoadManager() = default;
void ModelLoadManager::Configure(ModelTypeSet preferred_types_without_errors,
ModelTypeSet preferred_types,
const ConfigureContext& context) {
// |preferred_types_without_errors| must be a subset of |preferred_types|.
DCHECK(preferred_types.HasAll(preferred_types_without_errors))
<< " desired: "
<< ModelTypeSetToDebugString(preferred_types_without_errors)
<< ", preferred: " << ModelTypeSetToDebugString(preferred_types);
const bool sync_mode_changed =
configure_context_.has_value() &&
configure_context_->sync_mode != context.sync_mode;
configure_context_ = context;
// Only keep types that have controllers.
preferred_types_without_errors_.Clear();
for (ModelType type : preferred_types_without_errors) {
auto dtc_iter = controllers_->find(type);
if (dtc_iter != controllers_->end()) {
const 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);
preferred_types_without_errors_.Put(type);
}
}
DVLOG(1) << "ModelLoadManager: Initializing for "
<< ModelTypeSetToDebugString(preferred_types_without_errors_);
notified_about_ready_for_configure_ = false;
if (sync_mode_changed) {
// When the sync mode changes (between full-sync and transport mode),
// restart all data types so that they can re-wire to the correct storage.
DVLOG(1) << "ModelLoadManager: Stopping all types because the sync mode "
"changed.";
for (const auto& [type, dtc] : *controllers_) {
// Use CLEAR_METADATA in this case to avoid that two independent model
// instances maintain their own copy of sync metadata.
if (dtc->state() != DataTypeController::NOT_RUNNING ||
base::FeatureList::IsEnabled(
kSyncAllowClearingMetadataWhenDataTypeIsStopped)) {
StopDatatypeImpl(SyncError(), SyncStopMetadataFate::CLEAR_METADATA,
dtc.get(), base::DoNothing());
}
}
} else {
// If the sync mode hasn't changed, stop only the types that are not
// preferred anymore.
DVLOG(1) << "ModelLoadManager: Stopping disabled types.";
for (const auto& [type, dtc] : *controllers_) {
if (!preferred_types_without_errors_.Has(dtc->type()) &&
dtc->state() != DataTypeController::NOT_RUNNING) {
SyncStopMetadataFate metadata_fate =
preferred_types.Has(dtc->type())
? SyncStopMetadataFate::KEEP_METADATA
: SyncStopMetadataFate::CLEAR_METADATA;
DVLOG(1) << "ModelLoadManager: stop " << dtc->name()
<< " with metadata fate " << static_cast<int>(metadata_fate);
StopDatatypeImpl(SyncError(), metadata_fate, dtc.get(),
base::DoNothing());
}
}
}
// Note: At this point, some types may still be in the STOPPING state, i.e.
// they cannot be loaded right now. LoadDesiredTypes() takes care to wait for
// the desired types to finish stopping before starting them again. And for
// undesired types, it doesn't matter in what state they are.
LoadDesiredTypes();
}
void ModelLoadManager::StopDatatype(ModelType type,
SyncStopMetadataFate metadata_fate,
SyncError error) {
DCHECK(error.IsSet());
preferred_types_without_errors_.Remove(type);
DataTypeController* dtc = controllers_->find(type)->second.get();
// If the feature flag is enabled, call stop on data types even if they are
// already stopped since we may still want to clear the metadata.
if (base::FeatureList::IsEnabled(
kSyncAllowClearingMetadataWhenDataTypeIsStopped) ||
(dtc->state() != DataTypeController::NOT_RUNNING &&
dtc->state() != DataTypeController::STOPPING)) {
StopDatatypeImpl(error, metadata_fate, dtc, base::DoNothing());
}
// Removing a desired type may mean all models are now loaded.
NotifyDelegateIfReadyForConfigure();
}
void ModelLoadManager::StopDatatypeImpl(
const SyncError& error,
SyncStopMetadataFate metadata_fate,
DataTypeController* dtc,
DataTypeController::StopCallback callback) {
loaded_types_.Remove(dtc->type());
DCHECK(base::FeatureList::IsEnabled(
syncer::kSyncAllowClearingMetadataWhenDataTypeIsStopped) ||
error.IsSet() || (dtc->state() != DataTypeController::NOT_RUNNING));
delegate_->OnSingleDataTypeWillStop(dtc->type(), error);
// Note: Depending on |metadata_fate|, data types will clear their metadata
// in response to Stop().
dtc->Stop(metadata_fate, std::move(callback));
}
void ModelLoadManager::LoadDesiredTypes() {
// Note: |preferred_types_without_errors_| might be modified during iteration
// (e.g. in ModelLoadCallback()), so make a copy.
const ModelTypeSet types = preferred_types_without_errors_;
// Start timer to measure time for loading to complete.
load_models_elapsed_timer_ = std::make_unique<base::ElapsedTimer>();
for (ModelType type : types) {
auto dtc_iter = controllers_->find(type);
DCHECK(dtc_iter != controllers_->end());
DataTypeController* dtc = dtc_iter->second.get();
auto model_load_callback = base::BindRepeating(
&ModelLoadManager::ModelLoadCallback, weak_ptr_factory_.GetWeakPtr());
if (dtc->state() == DataTypeController::NOT_RUNNING) {
DCHECK(!loaded_types_.Has(dtc->type()));
dtc->LoadModels(*configure_context_, std::move(model_load_callback));
} else if (dtc->state() == DataTypeController::STOPPING) {
// If the datatype is already STOPPING, we wait for it to stop before
// starting it up again.
auto stop_callback =
base::BindRepeating(&DataTypeController::LoadModels,
// This should be safe since the stop callback is
// called from the DataTypeController.
base::Unretained(dtc), *configure_context_,
std::move(model_load_callback));
DCHECK(!loaded_types_.Has(dtc->type()));
dtc->Stop(SyncStopMetadataFate::KEEP_METADATA, std::move(stop_callback));
}
}
if (base::FeatureList::IsEnabled(syncer::kSyncEnableLoadModelsTimeout)) {
// Start a timeout timer for load.
load_models_timeout_timer_.Start(FROM_HERE,
kSyncLoadModelsTimeoutDuration.Get(), this,
&ModelLoadManager::OnLoadModelsTimeout);
}
// It's possible that all models are already loaded.
NotifyDelegateIfReadyForConfigure();
}
void ModelLoadManager::Stop(SyncStopMetadataFate metadata_fate) {
// Ignore callbacks from controllers.
weak_ptr_factory_.InvalidateWeakPtrs();
// Stop all data types. Note that if the feature flag is enabled, we are also
// calling stop on data types that are already stopped since we may still want
// to clear the metadata.
for (const auto& [type, dtc] : *controllers_) {
if (base::FeatureList::IsEnabled(
kSyncAllowClearingMetadataWhenDataTypeIsStopped) ||
(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 Configure() is called.
StopDatatypeImpl(SyncError(), metadata_fate, dtc.get(),
base::DoNothing());
DVLOG(1) << "ModelLoadManager: Stopped " << dtc->name();
}
}
preferred_types_without_errors_.Clear();
loaded_types_.Clear();
}
void ModelLoadManager::ModelLoadCallback(ModelType type,
const SyncError& error) {
DVLOG(1) << "ModelLoadManager: ModelLoadCallback for "
<< ModelTypeToDebugString(type);
if (error.IsSet()) {
DVLOG(1) << "ModelLoadManager: Type encountered an error.";
preferred_types_without_errors_.Remove(type);
DataTypeController* dtc = controllers_->find(type)->second.get();
StopDatatypeImpl(error, SyncStopMetadataFate::KEEP_METADATA, dtc,
base::DoNothing());
NotifyDelegateIfReadyForConfigure();
return;
}
// This happens when slow loading type is disabled by new configuration or
// the model came unready during loading.
if (!preferred_types_without_errors_.Has(type)) {
return;
}
DCHECK(!loaded_types_.Has(type));
loaded_types_.Put(type);
NotifyDelegateIfReadyForConfigure();
}
void ModelLoadManager::NotifyDelegateIfReadyForConfigure() {
if (notified_about_ready_for_configure_)
return;
if (!loaded_types_.HasAll(preferred_types_without_errors_)) {
// At least one type is not ready.
return;
}
// It may be possible that `load_models_elapsed_timer_` was never set, e.g.
// if StopDatatype() was called before Configure().
if (load_models_elapsed_timer_) {
base::UmaHistogramMediumTimes("Sync.ModelLoadManager.LoadModelsElapsedTime",
load_models_elapsed_timer_->Elapsed());
// Needs to be measured only when NotifyDelegateIfReadyForConfigure() is
// called for the first time after all types have been loaded.
load_models_elapsed_timer_.reset();
}
// Cancel the timer since all the desired types are now loaded.
load_models_timeout_timer_.Stop();
notified_about_ready_for_configure_ = true;
delegate_->OnAllDataTypesReadyForConfigure();
}
void ModelLoadManager::OnLoadModelsTimeout() {
DCHECK(base::FeatureList::IsEnabled(syncer::kSyncEnableLoadModelsTimeout));
// TODO(crbug.com/1420553): Investigate why the following DCHECK fails.
// DCHECK(!loaded_types_.HasAll(preferred_types_without_errors_));
const ModelTypeSet types = preferred_types_without_errors_;
for (ModelType type : types) {
if (!loaded_types_.Has(type)) {
base::UmaHistogramEnumeration("Sync.ModelLoadManager.LoadModelsTimeout",
ModelTypeHistogramValue(type));
// All the types which have not loaded yet are removed from
// `preferred_types_without_errors_`. This will cause ModelLoadCallback()
// to stop these types when they finish loading. The intention here is to
// not wait for these types and continue with connecting the loaded data
// types, while also ensuring the DataTypeManager does not think the
// datatype is stopped before the controller actually comes to a stopped
// state.
preferred_types_without_errors_.Remove(type);
}
}
// Stop waiting for the data types to load and go ahead with connecting the
// loaded types.
NotifyDelegateIfReadyForConfigure();
}
} // namespace syncer