| // 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/service/model_load_manager.h" | |
| #include <map> | |
| #include <utility> | |
| #include "base/barrier_closure.h" | |
| #include "base/debug/alias.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/data_type.h" | |
| #include "components/sync/base/features.h" | |
| #include "components/sync/base/sync_stop_metadata_fate.h" | |
| #include "components/sync/model/model_error.h" | |
| #include "components/sync/service/data_type_controller.h" | |
| #include "components/sync/service/sync_error.h" | |
| namespace syncer { | |
| namespace { | |
| bool ModelIsLoadedOrFailed(const DataTypeController& mtc) { | |
| switch (mtc.state()) { | |
| case DataTypeController::NOT_RUNNING: | |
| case DataTypeController::MODEL_STARTING: | |
| case DataTypeController::STOPPING: | |
| return false; | |
| case DataTypeController::MODEL_LOADED: | |
| case DataTypeController::RUNNING: | |
| case DataTypeController::FAILED: | |
| return true; | |
| } | |
| } | |
| } // namespace | |
| const base::TimeDelta kSyncLoadModelsTimeoutDuration = base::Seconds(30); | |
| ModelLoadManager::ModelLoadManager( | |
| const DataTypeController::TypeMap* controllers, | |
| ModelLoadManagerDelegate* processor) | |
| : controllers_(controllers), delegate_(processor) {} | |
| ModelLoadManager::~ModelLoadManager() = default; | |
| void ModelLoadManager::Configure(DataTypeSet preferred_types_without_errors, | |
| DataTypeSet 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: " | |
| << DataTypeSetToDebugString(preferred_types_without_errors) | |
| << ", preferred: " << DataTypeSetToDebugString(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 (DataType 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 or with preconditions not met should have | |
| // been filtered out by the DataTypeManager. | |
| CHECK_NE(dtc->state(), DataTypeController::FAILED); | |
| preferred_types_without_errors_.Put(type); | |
| } | |
| } | |
| DVLOG(1) << "ModelLoadManager: Initializing for " | |
| << DataTypeSetToDebugString(preferred_types_without_errors_); | |
| delegate_waiting_for_ready_for_configure_ = true; | |
| 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. | |
| StopDatatypeImpl(/*error=*/std::nullopt, | |
| 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())) { | |
| // Call Stop() even on types not running to allow clearing metadata. | |
| // This is useful to clear metadata for types which were disabled during | |
| // configuration. Also clear metadata depending on the precondition | |
| // state. | |
| SyncStopMetadataFate metadata_fate = | |
| SyncStopMetadataFate::KEEP_METADATA; | |
| if (!preferred_types.Has(dtc->type()) || | |
| dtc->GetPreconditionState(DataTypeController::PreconditionContext( | |
| configure_context_->account_managed_status)) == | |
| DataTypeController::PreconditionState::kMustStopAndClearData) { | |
| metadata_fate = SyncStopMetadataFate::CLEAR_METADATA; | |
| } | |
| DVLOG(1) << "ModelLoadManager: stop " << dtc->name() | |
| << " with metadata fate " << static_cast<int>(metadata_fate); | |
| StopDatatypeImpl(/*error=*/std::nullopt, 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(DataType type, | |
| SyncStopMetadataFate metadata_fate, | |
| SyncError error) { | |
| preferred_types_without_errors_.Remove(type); | |
| DataTypeController* dtc = controllers_->find(type)->second.get(); | |
| // Call stop on data types even if they are | |
| // already stopped since we may still want to clear the metadata. | |
| StopDatatypeImpl(error, metadata_fate, dtc, base::DoNothing()); | |
| // Removing a desired type may mean all models are now loaded. | |
| NotifyDelegateIfReadyForConfigure(); | |
| } | |
| void ModelLoadManager::StopDatatypeImpl( | |
| const std::optional<SyncError>& error, | |
| SyncStopMetadataFate metadata_fate, | |
| DataTypeController* dtc, | |
| DataTypeController::StopCallback callback) { | |
| const DataType data_type = dtc->type(); | |
| // Avoid that the local variable is optimized away, motivated by | |
| // crbug.com/1456872. | |
| base::debug::Alias(&data_type); | |
| delegate_->OnSingleDataTypeWillStop(data_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() { | |
| // Invalidate any previously-registered LoadModelsForType() callbacks (e.g. | |
| // from a previous Configure() call) to avoid calling LoadModelsForType() | |
| // multiple times for the same type. | |
| load_models_weak_ptr_factory_.InvalidateWeakPtrs(); | |
| // Note: `preferred_types_without_errors_` might be modified during iteration | |
| // (e.g. in ModelLoadCallback()), so make a copy. | |
| const DataTypeSet types = preferred_types_without_errors_; | |
| // Start timer to measure time for loading to complete. | |
| load_models_elapsed_timer_.emplace(); | |
| for (DataType type : types) { | |
| auto dtc_iter = controllers_->find(type); | |
| CHECK(dtc_iter != controllers_->end()); | |
| DataTypeController* dtc = dtc_iter->second.get(); | |
| if (dtc->state() == DataTypeController::NOT_RUNNING) { | |
| LoadModelsForType(dtc); | |
| } 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::BindOnce(&ModelLoadManager::LoadModelsForType, | |
| load_models_weak_ptr_factory_.GetWeakPtr(), dtc); | |
| dtc->Stop(SyncStopMetadataFate::KEEP_METADATA, std::move(stop_callback)); | |
| } | |
| } | |
| // Start a timeout timer for load. | |
| load_models_timeout_timer_.Start(FROM_HERE, kSyncLoadModelsTimeoutDuration, | |
| 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(); | |
| load_models_weak_ptr_factory_.InvalidateWeakPtrs(); | |
| // Stop all data types. Note that stop is also called on data types that are | |
| // already stopped to allow clearing the metadata. | |
| for (const auto& [type, dtc] : *controllers_) { | |
| // 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(/*error=*/std::nullopt, metadata_fate, dtc.get(), | |
| base::DoNothing()); | |
| DVLOG(1) << "ModelLoadManager: Stopped " << dtc->name(); | |
| } | |
| load_models_timeout_timer_.Stop(); | |
| delegate_waiting_for_ready_for_configure_ = false; | |
| preferred_types_without_errors_.Clear(); | |
| } | |
| void ModelLoadManager::ModelLoadCallback( | |
| DataType type, | |
| const std::optional<ModelError>& error) { | |
| DVLOG(1) << "ModelLoadManager: ModelLoadCallback for " | |
| << DataTypeToDebugString(type); | |
| if (error.has_value()) { | |
| DVLOG(1) << "ModelLoadManager: Type encountered an error."; | |
| preferred_types_without_errors_.Remove(type); | |
| DataTypeController* dtc = controllers_->find(type)->second.get(); | |
| StopDatatypeImpl(SyncError::CreateFromModelError(*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; | |
| } | |
| NotifyDelegateIfReadyForConfigure(); | |
| } | |
| void ModelLoadManager::NotifyDelegateIfReadyForConfigure() { | |
| if (!delegate_waiting_for_ready_for_configure_) { | |
| return; | |
| } | |
| // Check (and early-return) if any type is not ready. | |
| for (DataType type : preferred_types_without_errors_) { | |
| if (!ModelIsLoadedOrFailed(*controllers_->find(type)->second)) { | |
| 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(); | |
| delegate_waiting_for_ready_for_configure_ = false; | |
| delegate_->OnAllDataTypesReadyForConfigure(); | |
| } | |
| void ModelLoadManager::OnLoadModelsTimeout() { | |
| const DataTypeSet types = preferred_types_without_errors_; | |
| for (DataType type : types) { | |
| if (!ModelIsLoadedOrFailed(*controllers_->find(type)->second)) { | |
| base::UmaHistogramEnumeration("Sync.ModelLoadManager.LoadModelsTimeout", | |
| DataTypeHistogramValue(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(); | |
| } | |
| void ModelLoadManager::LoadModelsForType(DataTypeController* dtc) { | |
| // FAILED is possible if the type was STOPPING but then encountered an error | |
| // before the type actually stopped. | |
| if (dtc->state() == DataTypeController::FAILED) { | |
| ModelLoadCallback( | |
| dtc->type(), | |
| ModelError(FROM_HERE, | |
| ModelError::Type::kModelLoadManagerDataTypeInFailedState)); | |
| return; | |
| } | |
| CHECK_EQ(dtc->state(), DataTypeController::NOT_RUNNING); | |
| dtc->LoadModels( | |
| *configure_context_, | |
| base::BindRepeating(&ModelLoadManager::ModelLoadCallback, | |
| weak_ptr_factory_.GetWeakPtr(), dtc->type())); | |
| } | |
| } // namespace syncer |