| // 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_type_controller.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "components/signin/core/browser/account_info.h" |
| #include "components/sync/base/data_type_histogram.h" |
| #include "components/sync/driver/configure_context.h" |
| #include "components/sync/engine/data_type_activation_response.h" |
| #include "components/sync/engine/model_type_configurer.h" |
| #include "components/sync/model/data_type_activation_request.h" |
| #include "components/sync/model/data_type_error_handler_impl.h" |
| #include "components/sync/model/sync_merge_result.h" |
| |
| namespace syncer { |
| namespace { |
| |
| void ReportErrorOnModelThread( |
| scoped_refptr<base::SequencedTaskRunner> ui_thread, |
| const ModelErrorHandler& error_handler, |
| const ModelError& error) { |
| ui_thread->PostTask(error.location(), base::BindOnce(error_handler, error)); |
| } |
| |
| // Takes the strictest policy for clearing sync metadata. |
| SyncStopMetadataFate TakeStrictestMetadataFate(SyncStopMetadataFate fate1, |
| SyncStopMetadataFate fate2) { |
| switch (fate1) { |
| case CLEAR_METADATA: |
| return CLEAR_METADATA; |
| case KEEP_METADATA: |
| return fate2; |
| } |
| NOTREACHED(); |
| return KEEP_METADATA; |
| } |
| |
| } // namespace |
| |
| ModelTypeController::ModelTypeController( |
| ModelType type, |
| std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk) |
| : DataTypeController(type) { |
| delegate_map_.emplace(STORAGE_ON_DISK, std::move(delegate_on_disk)); |
| } |
| |
| ModelTypeController::ModelTypeController( |
| ModelType type, |
| std::unique_ptr<ModelTypeControllerDelegate> delegate_on_disk, |
| std::unique_ptr<ModelTypeControllerDelegate> delegate_in_memory) |
| : ModelTypeController(type, std::move(delegate_on_disk)) { |
| delegate_map_.emplace(STORAGE_IN_MEMORY, std::move(delegate_in_memory)); |
| } |
| |
| ModelTypeController::~ModelTypeController() {} |
| |
| bool ModelTypeController::ShouldLoadModelBeforeConfigure() const { |
| // USS datatypes require loading models because model controls storage where |
| // data type context and progress marker are persisted. |
| return true; |
| } |
| |
| void ModelTypeController::LoadModels( |
| const ConfigureContext& configure_context, |
| const ModelLoadCallback& model_load_callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(model_load_callback); |
| DCHECK_EQ(NOT_RUNNING, state_); |
| |
| auto it = delegate_map_.find(configure_context.storage_option); |
| DCHECK(it != delegate_map_.end()); |
| delegate_ = it->second.get(); |
| DCHECK(delegate_); |
| |
| DVLOG(1) << "Sync starting for " << ModelTypeToString(type()); |
| state_ = MODEL_STARTING; |
| model_load_callback_ = model_load_callback; |
| |
| DataTypeActivationRequest request; |
| request.error_handler = base::BindRepeating( |
| &ReportErrorOnModelThread, base::SequencedTaskRunnerHandle::Get(), |
| base::BindRepeating(&ModelTypeController::ReportModelError, |
| base::AsWeakPtr(this), SyncError::DATATYPE_ERROR)); |
| request.authenticated_account_id = configure_context.authenticated_account_id; |
| request.cache_guid = configure_context.cache_guid; |
| request.storage_option = configure_context.storage_option; |
| request.configuration_start_time = configure_context.configuration_start_time; |
| |
| // Note that |request.authenticated_account_id| may be empty for local sync. |
| DCHECK(!request.cache_guid.empty()); |
| |
| // Ask the delegate to actually start the datatype. |
| delegate_->OnSyncStarting( |
| request, base::BindOnce(&ModelTypeController::OnDelegateStarted, |
| base::AsWeakPtr(this))); |
| } |
| |
| void ModelTypeController::BeforeLoadModels(ModelTypeConfigurer* configurer) {} |
| |
| void ModelTypeController::RegisterWithBackend( |
| base::OnceCallback<void(bool)> set_downloaded, |
| ModelTypeConfigurer* configurer) { |
| DCHECK(CalledOnValidThread()); |
| if (activated_) |
| return; |
| DCHECK(configurer); |
| DCHECK(activation_response_); |
| DCHECK_EQ(MODEL_LOADED, state_); |
| // Inform the DataTypeManager whether our initial download is complete. |
| std::move(set_downloaded) |
| .Run(activation_response_->model_type_state.initial_sync_done()); |
| // Pass activation context to ModelTypeRegistry, where ModelTypeWorker gets |
| // created and connected with the delegate (processor). |
| configurer->ActivateNonBlockingDataType(type(), |
| std::move(activation_response_)); |
| activated_ = true; |
| } |
| |
| void ModelTypeController::StartAssociating(StartCallback start_callback) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(start_callback); |
| DCHECK_EQ(MODEL_LOADED, state_); |
| |
| state_ = RUNNING; |
| DVLOG(1) << "Sync running for " << ModelTypeToString(type()); |
| |
| // There is no association, just call back promptly. |
| SyncMergeResult merge_result(type()); |
| std::move(start_callback).Run(OK, merge_result, merge_result); |
| } |
| |
| void ModelTypeController::ActivateDataType(ModelTypeConfigurer* configurer) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(configurer); |
| DCHECK_EQ(RUNNING, state_); |
| // In contrast with directory datatypes, non-blocking data types should be |
| // activated in RegisterWithBackend. activation_response_ should be |
| // passed to backend before call to ActivateDataType. |
| DCHECK(!activation_response_); |
| } |
| |
| void ModelTypeController::DeactivateDataType(ModelTypeConfigurer* configurer) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(configurer); |
| if (activated_) { |
| configurer->DeactivateNonBlockingDataType(type()); |
| activated_ = false; |
| } |
| } |
| |
| void ModelTypeController::Stop(ShutdownReason shutdown_reason, |
| StopCallback callback) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Leave metadata if we do not disable sync completely. |
| SyncStopMetadataFate metadata_fate = KEEP_METADATA; |
| switch (shutdown_reason) { |
| case STOP_SYNC: |
| break; |
| case DISABLE_SYNC: |
| metadata_fate = CLEAR_METADATA; |
| break; |
| case BROWSER_SHUTDOWN: |
| break; |
| } |
| |
| switch (state()) { |
| case ASSOCIATING: |
| // We don't really use this state in this class. |
| NOTREACHED(); |
| break; |
| |
| case NOT_RUNNING: |
| case FAILED: |
| // Nothing to stop. |metadata_fate| might require CLEAR_METADATA, |
| // which could lead to leaking sync metadata, but it doesn't seem a |
| // realistic scenario (disable sync during shutdown?). |
| std::move(callback).Run(); |
| return; |
| |
| case STOPPING: |
| DCHECK(!model_stop_callbacks_.empty()); |
| model_stop_metadata_fate_ = |
| TakeStrictestMetadataFate(model_stop_metadata_fate_, metadata_fate); |
| model_stop_callbacks_.push_back(std::move(callback)); |
| break; |
| |
| case MODEL_STARTING: |
| DCHECK(model_load_callback_); |
| DCHECK(model_stop_callbacks_.empty()); |
| DLOG(WARNING) << "Deferring stop for " << ModelTypeToString(type()) |
| << " because it's still starting"; |
| model_load_callback_.Reset(); |
| model_stop_metadata_fate_ = metadata_fate; |
| model_stop_callbacks_.push_back(std::move(callback)); |
| // The actual stop will be executed when the starting process is finished. |
| state_ = STOPPING; |
| break; |
| |
| case MODEL_LOADED: |
| case RUNNING: |
| DVLOG(1) << "Stopping sync for " << ModelTypeToString(type()); |
| model_load_callback_.Reset(); |
| state_ = NOT_RUNNING; |
| delegate_->OnSyncStopping(metadata_fate); |
| delegate_ = nullptr; |
| std::move(callback).Run(); |
| break; |
| } |
| } |
| |
| DataTypeController::State ModelTypeController::state() const { |
| return state_; |
| } |
| |
| void ModelTypeController::GetAllNodes(AllNodesCallback callback) { |
| DCHECK(delegate_); |
| delegate_->GetAllNodesForDebugging(std::move(callback)); |
| } |
| |
| void ModelTypeController::GetStatusCounters(StatusCountersCallback callback) { |
| DCHECK(delegate_); |
| delegate_->GetStatusCountersForDebugging(std::move(callback)); |
| } |
| |
| void ModelTypeController::RecordMemoryUsageAndCountsHistograms() { |
| DCHECK(delegate_); |
| delegate_->RecordMemoryUsageAndCountsHistograms(); |
| } |
| |
| void ModelTypeController::ReportModelError(SyncError::ErrorType error_type, |
| const ModelError& error) { |
| DCHECK(CalledOnValidThread()); |
| |
| // TODO(crbug.com/890729): This is obviously misplaced/misnamed as we report |
| // run-time failures as well. Rename the histogram to ConfigureResult and |
| // report it only after startup (also for success). |
| if (state_ != NOT_RUNNING) { |
| #define PER_DATA_TYPE_MACRO(type_str) \ |
| UMA_HISTOGRAM_ENUMERATION("Sync." type_str "ConfigureFailure", \ |
| UNRECOVERABLE_ERROR, MAX_CONFIGURE_RESULT); |
| SYNC_DATA_TYPE_HISTOGRAM(type()); |
| #undef PER_DATA_TYPE_MACRO |
| } |
| |
| switch (state_) { |
| case MODEL_LOADED: |
| // Fall through. Before state_ is flipped to RUNNING, we treat it as a |
| // start failure. This is somewhat arbitrary choice. |
| case STOPPING: |
| // Fall through. We treat it the same as starting as this means stopping was |
| // requested while starting the data type. |
| case MODEL_STARTING: |
| RecordStartFailure(); |
| break; |
| case RUNNING: |
| RecordRunFailure(); |
| break; |
| case NOT_RUNNING: |
| // Error could arrive too late, e.g. after the datatype has been stopped. |
| // This is allowed for the delegate's convenience, so there's no |
| // constraints around when exactly |
| // DataTypeActivationRequest::error_handler is supposed to be used (it can |
| // be used at any time). This also simplifies the implementation of |
| // task-posting delegates. |
| state_ = FAILED; |
| return; |
| case FAILED: |
| // Do not record for the second time and exit early. |
| return; |
| case ASSOCIATING: |
| // Not possible, we do not use associating in this class. |
| NOTREACHED(); |
| } |
| |
| state_ = FAILED; |
| |
| TriggerCompletionCallbacks( |
| SyncError(error.location(), error_type, error.message(), type())); |
| } |
| |
| void ModelTypeController::RecordStartFailure() const { |
| DCHECK(CalledOnValidThread()); |
| // TODO(wychen): enum uma should be strongly typed. crbug.com/661401 |
| // This is not strongly typed because historically, ModelTypeToHistogramInt() |
| // defines quite a different order from the type() enum. |
| UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures2", |
| ModelTypeToHistogramInt(type()), |
| static_cast<int>(ModelType::NUM_ENTRIES)); |
| } |
| |
| void ModelTypeController::RecordRunFailure() const { |
| DCHECK(CalledOnValidThread()); |
| // TODO(wychen): enum uma should be strongly typed. crbug.com/661401 |
| // This is not strongly typed because historically, ModelTypeToHistogramInt() |
| // defines quite a different order from the type() enum. |
| UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures2", |
| ModelTypeToHistogramInt(type()), |
| static_cast<int>(ModelType::NUM_ENTRIES)); |
| } |
| |
| void ModelTypeController::OnDelegateStarted( |
| std::unique_ptr<DataTypeActivationResponse> activation_response) { |
| DCHECK(CalledOnValidThread()); |
| |
| switch (state_) { |
| case STOPPING: |
| DCHECK(!model_stop_callbacks_.empty()); |
| DCHECK(!model_load_callback_); |
| state_ = NOT_RUNNING; |
| FALLTHROUGH; |
| case FAILED: |
| DVLOG(1) << "Successful sync start completion received late for " |
| << ModelTypeToString(type()) |
| << ", it has been stopped meanwhile"; |
| delegate_->OnSyncStopping(model_stop_metadata_fate_); |
| delegate_ = nullptr; |
| break; |
| case MODEL_STARTING: |
| DCHECK(model_stop_callbacks_.empty()); |
| // Hold on to the activation context until ActivateDataType is called. |
| activation_response_ = std::move(activation_response); |
| state_ = MODEL_LOADED; |
| DVLOG(1) << "Sync start completed for " << ModelTypeToString(type()); |
| break; |
| case MODEL_LOADED: |
| case RUNNING: |
| case NOT_RUNNING: |
| case ASSOCIATING: |
| NOTREACHED() << " type " << ModelTypeToString(type()) << " state " |
| << StateToString(state_); |
| } |
| |
| TriggerCompletionCallbacks(SyncError()); |
| } |
| |
| void ModelTypeController::TriggerCompletionCallbacks(const SyncError& error) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (model_load_callback_) { |
| DCHECK(model_stop_callbacks_.empty()); |
| DCHECK(state_ == MODEL_LOADED || state_ == FAILED); |
| |
| model_load_callback_.Run(type(), error); |
| } else if (!model_stop_callbacks_.empty()) { |
| // State FAILED is possible if an error occurred during STOPPING, either |
| // because the load failed or because ReportModelError() was called |
| // directly by a subclass. |
| DCHECK(state_ == NOT_RUNNING || state_ == FAILED); |
| |
| // We make a copy in case running the callbacks has side effects and |
| // modifies the vector, although we don't expect that in practice. |
| std::vector<StopCallback> model_stop_callbacks = |
| std::move(model_stop_callbacks_); |
| DCHECK(model_stop_callbacks_.empty()); |
| for (StopCallback& stop_callback : model_stop_callbacks) { |
| std::move(stop_callback).Run(); |
| } |
| } |
| } |
| |
| } // namespace syncer |