| // Copyright (c) 2012 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 "chrome/browser/sync/glue/model_association_manager.h" |
| |
| #include <algorithm> |
| #include <functional> |
| |
| #include "base/debug/trace_event.h" |
| #include "base/logging.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/metrics/histogram.h" |
| #include "sync/internal_api/public/base/model_type.h" |
| |
| using content::BrowserThread; |
| using syncer::ModelTypeSet; |
| |
| namespace browser_sync { |
| // The amount of time we wait for a datatype to load. If the type has |
| // not finished loading we move on to the next type. Once this type |
| // finishes loading we will do a configure to associate this type. Note |
| // that in most cases types finish loading before this timeout. |
| const int64 kDataTypeLoadWaitTimeInSeconds = 120; |
| namespace { |
| |
| static const syncer::ModelType kStartOrder[] = { |
| syncer::NIGORI, // Listed for completeness. |
| syncer::DEVICE_INFO, // Listed for completeness. |
| syncer::EXPERIMENTS, // Listed for completeness. |
| syncer::PROXY_TABS, // Listed for completeness. |
| syncer::BOOKMARKS, // UI thread datatypes. |
| syncer::MANAGED_USERS, // Syncing managed users on initial login might |
| // block creating a new managed user, so we |
| // want to do it early. |
| syncer::PREFERENCES, |
| syncer::PRIORITY_PREFERENCES, |
| syncer::EXTENSIONS, |
| syncer::APPS, |
| syncer::THEMES, |
| syncer::SEARCH_ENGINES, |
| syncer::SESSIONS, |
| syncer::APP_NOTIFICATIONS, |
| syncer::DICTIONARY, |
| syncer::FAVICON_IMAGES, |
| syncer::FAVICON_TRACKING, |
| syncer::MANAGED_USER_SETTINGS, |
| syncer::ARTICLES, |
| syncer::AUTOFILL, // Non-UI thread datatypes. |
| syncer::AUTOFILL_PROFILE, |
| syncer::EXTENSION_SETTINGS, |
| syncer::APP_SETTINGS, |
| syncer::TYPED_URLS, |
| syncer::PASSWORDS, |
| syncer::HISTORY_DELETE_DIRECTIVES, |
| syncer::SYNCED_NOTIFICATIONS, |
| }; |
| |
| COMPILE_ASSERT(arraysize(kStartOrder) == |
| syncer::MODEL_TYPE_COUNT - syncer::FIRST_REAL_MODEL_TYPE, |
| kStartOrder_IncorrectSize); |
| |
| // Comparator used when sorting data type controllers. |
| class SortComparator : public std::binary_function<DataTypeController*, |
| DataTypeController*, |
| bool> { |
| public: |
| explicit SortComparator(std::map<syncer::ModelType, int>* order) |
| : order_(order) { } |
| |
| // Returns true if lhs precedes rhs. |
| bool operator() (DataTypeController* lhs, DataTypeController* rhs) { |
| return (*order_)[lhs->type()] < (*order_)[rhs->type()]; |
| } |
| |
| private: |
| std::map<syncer::ModelType, int>* order_; |
| }; |
| |
| syncer::DataTypeAssociationStats BuildAssociationStatsFromMergeResults( |
| const syncer::SyncMergeResult& local_merge_result, |
| const syncer::SyncMergeResult& syncer_merge_result, |
| const base::TimeDelta& association_wait_time, |
| const base::TimeDelta& association_time) { |
| DCHECK_EQ(local_merge_result.model_type(), syncer_merge_result.model_type()); |
| syncer::DataTypeAssociationStats stats; |
| stats.had_error = local_merge_result.error().IsSet() || |
| syncer_merge_result.error().IsSet(); |
| stats.num_local_items_before_association = |
| local_merge_result.num_items_before_association(); |
| stats.num_sync_items_before_association = |
| syncer_merge_result.num_items_before_association(); |
| stats.num_local_items_after_association = |
| local_merge_result.num_items_after_association(); |
| stats.num_sync_items_after_association = |
| syncer_merge_result.num_items_after_association(); |
| stats.num_local_items_added = |
| local_merge_result.num_items_added(); |
| stats.num_local_items_deleted = |
| local_merge_result.num_items_deleted(); |
| stats.num_local_items_modified = |
| local_merge_result.num_items_modified(); |
| stats.local_version_pre_association = |
| local_merge_result.pre_association_version(); |
| stats.num_sync_items_added = |
| syncer_merge_result.num_items_added(); |
| stats.num_sync_items_deleted = |
| syncer_merge_result.num_items_deleted(); |
| stats.num_sync_items_modified = |
| syncer_merge_result.num_items_modified(); |
| stats.sync_version_pre_association = |
| syncer_merge_result.pre_association_version(); |
| stats.association_wait_time = association_wait_time; |
| stats.association_time = association_time; |
| return stats; |
| } |
| |
| } // namespace |
| |
| ModelAssociationManager::ModelAssociationManager( |
| const DataTypeController::TypeMap* controllers, |
| ModelAssociationResultProcessor* processor) |
| : state_(IDLE), |
| currently_associating_(NULL), |
| controllers_(controllers), |
| result_processor_(processor), |
| weak_ptr_factory_(this) { |
| |
| // Ensure all data type controllers are stopped. |
| for (DataTypeController::TypeMap::const_iterator it = controllers_->begin(); |
| it != controllers_->end(); ++it) { |
| DCHECK_EQ(DataTypeController::NOT_RUNNING, (*it).second->state()); |
| } |
| |
| // Build a ModelType -> order map for sorting. |
| for (int i = 0; i < static_cast<int>(arraysize(kStartOrder)); i++) |
| start_order_[kStartOrder[i]] = i; |
| } |
| |
| ModelAssociationManager::~ModelAssociationManager() { |
| } |
| |
| void ModelAssociationManager::Initialize(syncer::ModelTypeSet desired_types) { |
| // TODO(tim): Bug 134550. CHECKing to ensure no reentrancy on dev channel. |
| // Remove this. |
| CHECK_EQ(state_, IDLE); |
| needs_start_.clear(); |
| needs_stop_.clear(); |
| failed_data_types_info_.clear(); |
| associating_types_.Clear(); |
| needs_crypto_types_.Clear(); |
| state_ = INITIALIZED_TO_CONFIGURE; |
| |
| DVLOG(1) << "ModelAssociationManager: Initializing for " |
| << syncer::ModelTypeSetToString(desired_types); |
| |
| // Stop the types that are still loading from the previous configuration. |
| // If they are enabled we will start them here once again. |
| for (std::vector<DataTypeController*>::const_iterator it = |
| pending_model_load_.begin(); |
| it != pending_model_load_.end(); |
| ++it) { |
| DVLOG(1) << "ModelAssociationManager: Stopping " |
| << (*it)->name() |
| << " before initialization"; |
| (*it)->Stop(); |
| } |
| |
| pending_model_load_.clear(); |
| waiting_to_associate_.clear(); |
| currently_associating_ = NULL; |
| |
| // Add any data type controllers into that needs_stop_ list that are |
| // currently MODEL_STARTING, ASSOCIATING, RUNNING or DISABLED. |
| for (DataTypeController::TypeMap::const_iterator it = controllers_->begin(); |
| it != controllers_->end(); ++it) { |
| DataTypeController* dtc = (*it).second.get(); |
| if (!desired_types.Has(dtc->type()) && |
| (dtc->state() == DataTypeController::MODEL_STARTING || |
| dtc->state() == DataTypeController::ASSOCIATING || |
| dtc->state() == DataTypeController::RUNNING || |
| dtc->state() == DataTypeController::DISABLED)) { |
| needs_stop_.push_back(dtc); |
| DVLOG(1) << "ModelTypeToString: Will stop " << dtc->name(); |
| } |
| } |
| // Sort these according to kStartOrder. |
| std::sort(needs_stop_.begin(), |
| needs_stop_.end(), |
| SortComparator(&start_order_)); |
| } |
| |
| void ModelAssociationManager::StartAssociationAsync( |
| const syncer::ModelTypeSet& types_to_associate) { |
| DCHECK(state_ == INITIALIZED_TO_CONFIGURE || state_ == IDLE); |
| state_ = CONFIGURING; |
| |
| // Calculate |needs_start_| list. |
| associating_types_ = types_to_associate; |
| GetControllersNeedingStart(&needs_start_); |
| // Sort these according to kStartOrder. |
| std::sort(needs_start_.begin(), |
| needs_start_.end(), |
| SortComparator(&start_order_)); |
| |
| DVLOG(1) << "ModelAssociationManager: Going to start model association"; |
| association_start_time_ = base::Time::Now(); |
| LoadModelForNextType(); |
| } |
| |
| void ModelAssociationManager::ResetForReconfiguration() { |
| state_ = IDLE; |
| DVLOG(1) << "ModelAssociationManager: Reseting for reconfiguration"; |
| needs_start_.clear(); |
| needs_stop_.clear(); |
| associating_types_.Clear(); |
| failed_data_types_info_.clear(); |
| needs_crypto_types_.Clear(); |
| } |
| |
| void ModelAssociationManager::StopDisabledTypes() { |
| DVLOG(1) << "ModelAssociationManager: Stopping disabled types."; |
| // Stop requested data types. |
| for (size_t i = 0; i < needs_stop_.size(); ++i) { |
| DVLOG(1) << "ModelAssociationManager: Stopping " << needs_stop_[i]->name(); |
| needs_stop_[i]->Stop(); |
| } |
| needs_stop_.clear(); |
| } |
| |
| void ModelAssociationManager::Stop() { |
| bool need_to_call_model_association_done = false; |
| DVLOG(1) << "ModelAssociationManager: Stopping MAM"; |
| if (state_ == CONFIGURING) { |
| DVLOG(1) << "ModelAssociationManager: In the middle of configuration while" |
| << " stopping"; |
| state_ = ABORTED; |
| DCHECK(currently_associating_ != NULL || |
| needs_start_.size() > 0 || |
| pending_model_load_.size() > 0 || |
| waiting_to_associate_.size() > 0); |
| |
| if (currently_associating_) { |
| TRACE_EVENT_ASYNC_END1("sync", "ModelAssociation", |
| currently_associating_, |
| "DataType", |
| ModelTypeToString(currently_associating_->type())); |
| DVLOG(1) << "ModelAssociationManager: stopping " |
| << currently_associating_->name(); |
| currently_associating_->Stop(); |
| } else { |
| // DTCs in other lists would be stopped below. |
| state_ = IDLE; |
| } |
| |
| DCHECK_EQ(IDLE, state_); |
| |
| // We are in the midle of model association. We need to inform the caller |
| // so the caller can send notificationst to PSS layer. |
| need_to_call_model_association_done = true; |
| } |
| |
| // Now continue stopping any types that have already started. |
| DCHECK(state_ == IDLE || |
| state_ == INITIALIZED_TO_CONFIGURE); |
| for (DataTypeController::TypeMap::const_iterator it = controllers_->begin(); |
| it != controllers_->end(); ++it) { |
| DataTypeController* dtc = (*it).second.get(); |
| if (dtc->state() != DataTypeController::NOT_RUNNING && |
| dtc->state() != DataTypeController::STOPPING) { |
| dtc->Stop(); |
| DVLOG(1) << "ModelAssociationManager: Stopped " << dtc->name(); |
| } |
| } |
| |
| DataTypeManager::ConfigureResult result(DataTypeManager::ABORTED, |
| associating_types_, |
| failed_data_types_info_, |
| syncer::ModelTypeSet(), |
| needs_crypto_types_); |
| failed_data_types_info_.clear(); |
| needs_crypto_types_.Clear(); |
| if (need_to_call_model_association_done) { |
| DVLOG(1) << "ModelAssociationManager: Calling OnModelAssociationDone"; |
| result_processor_->OnModelAssociationDone(result); |
| } |
| } |
| |
| bool ModelAssociationManager::GetControllersNeedingStart( |
| std::vector<DataTypeController*>* needs_start) { |
| DVLOG(1) << "ModelAssociationManager: GetControllersNeedingStart"; |
| // Add any data type controllers into the needs_start_ list that are |
| // currently NOT_RUNNING or STOPPING. |
| bool found_any = false; |
| for (ModelTypeSet::Iterator it = associating_types_.First(); |
| it.Good(); it.Inc()) { |
| DataTypeController::TypeMap::const_iterator dtc = |
| controllers_->find(it.Get()); |
| if (dtc != controllers_->end() && |
| (dtc->second->state() == DataTypeController::NOT_RUNNING || |
| dtc->second->state() == DataTypeController::STOPPING)) { |
| found_any = true; |
| if (needs_start) |
| needs_start->push_back(dtc->second.get()); |
| if (dtc->second->state() == DataTypeController::DISABLED) { |
| DVLOG(1) << "ModelAssociationManager: Found "\ |
| << syncer::ModelTypeToString(dtc->second->type()) |
| << " in disabled state."; |
| } |
| } |
| } |
| return found_any; |
| } |
| |
| void ModelAssociationManager::AppendToFailedDatatypesAndLogError( |
| DataTypeController::StartResult result, |
| const syncer::SyncError& error) { |
| failed_data_types_info_[error.model_type()] = error; |
| LOG(ERROR) << "Failed to associate models for " |
| << syncer::ModelTypeToString(error.model_type()); |
| UMA_HISTOGRAM_ENUMERATION("Sync.ConfigureFailed", |
| ModelTypeToHistogramInt(error.model_type()), |
| syncer::MODEL_TYPE_COUNT); |
| } |
| |
| void ModelAssociationManager::TypeStartCallback( |
| DataTypeController::StartResult start_result, |
| const syncer::SyncMergeResult& local_merge_result, |
| const syncer::SyncMergeResult& syncer_merge_result) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); |
| TRACE_EVENT_ASYNC_END1("sync", "ModelAssociation", |
| currently_associating_, |
| "DataType", |
| ModelTypeToString(currently_associating_->type())); |
| |
| DVLOG(1) << "ModelAssociationManager: TypeStartCallback"; |
| if (state_ == ABORTED) { |
| // Now that we have finished with the current type we can stop |
| // if abort was called. |
| DVLOG(1) << "ModelAssociationManager: Doing an early return" |
| << " because of abort"; |
| state_ = IDLE; |
| return; |
| } |
| |
| DCHECK(state_ == CONFIGURING); |
| |
| // We are done with this type. Clear it. |
| DataTypeController* started_dtc = currently_associating_; |
| currently_associating_ = NULL; |
| |
| if (start_result == DataTypeController::ASSOCIATION_FAILED) { |
| DVLOG(1) << "ModelAssociationManager: Encountered a failed type"; |
| AppendToFailedDatatypesAndLogError(start_result, |
| local_merge_result.error()); |
| } else if (start_result == DataTypeController::NEEDS_CRYPTO) { |
| DVLOG(1) << "ModelAssociationManager: Encountered an undecryptable type"; |
| needs_crypto_types_.Put(started_dtc->type()); |
| } |
| |
| // Track the merge results if we succeeded or an association failure |
| // occurred. |
| if ((DataTypeController::IsSuccessfulResult(start_result) || |
| start_result == DataTypeController::ASSOCIATION_FAILED) && |
| syncer::ProtocolTypes().Has(local_merge_result.model_type())) { |
| base::TimeDelta association_wait_time = |
| current_type_association_start_time_ - association_start_time_; |
| base::TimeDelta association_time = |
| base::Time::Now() - current_type_association_start_time_; |
| syncer::DataTypeAssociationStats stats = |
| BuildAssociationStatsFromMergeResults(local_merge_result, |
| syncer_merge_result, |
| association_wait_time, |
| association_time); |
| result_processor_->OnSingleDataTypeAssociationDone( |
| local_merge_result.model_type(), stats); |
| } |
| |
| // If the type started normally, continue to the next type. |
| // If the type is waiting for the cryptographer, continue to the next type. |
| // Once the cryptographer is ready, we'll attempt to restart this type. |
| // If this type encountered a type specific error continue to the next type. |
| if (start_result == DataTypeController::NEEDS_CRYPTO || |
| DataTypeController::IsSuccessfulResult(start_result) || |
| start_result == DataTypeController::ASSOCIATION_FAILED) { |
| |
| DVLOG(1) << "ModelAssociationManager: type start callback returned " |
| << start_result << " so calling LoadModelForNextType"; |
| LoadModelForNextType(); |
| return; |
| } |
| |
| // Any other result requires reconfiguration. Pass it on through the callback. |
| LOG(ERROR) << "Failed to configure " << started_dtc->name(); |
| DCHECK(local_merge_result.error().IsSet()); |
| DCHECK_EQ(started_dtc->type(), local_merge_result.error().model_type()); |
| DataTypeManager::ConfigureStatus configure_status = |
| DataTypeManager::ABORTED; |
| switch (start_result) { |
| case DataTypeController::ABORTED: |
| configure_status = DataTypeManager::ABORTED; |
| break; |
| case DataTypeController::UNRECOVERABLE_ERROR: |
| configure_status = DataTypeManager::UNRECOVERABLE_ERROR; |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| std::map<syncer::ModelType, syncer::SyncError> errors; |
| errors[local_merge_result.model_type()] = local_merge_result.error(); |
| |
| // Put our state to idle. |
| state_ = IDLE; |
| |
| DataTypeManager::ConfigureResult configure_result(configure_status, |
| associating_types_, |
| errors, |
| syncer::ModelTypeSet(), |
| needs_crypto_types_); |
| result_processor_->OnModelAssociationDone(configure_result); |
| } |
| |
| void ModelAssociationManager::LoadModelForNextType() { |
| DVLOG(1) << "ModelAssociationManager: LoadModelForNextType"; |
| if (!needs_start_.empty()) { |
| DVLOG(1) << "ModelAssociationManager: Starting " << needs_start_[0]->name(); |
| |
| DataTypeController* dtc = needs_start_[0]; |
| needs_start_.erase(needs_start_.begin()); |
| // Move from |needs_start_| to |pending_model_load_|. |
| pending_model_load_.insert(pending_model_load_.begin(), dtc); |
| timer_.Start(FROM_HERE, |
| base::TimeDelta::FromSeconds(kDataTypeLoadWaitTimeInSeconds), |
| this, |
| &ModelAssociationManager::LoadModelForNextType); |
| dtc->LoadModels(base::Bind( |
| &ModelAssociationManager::ModelLoadCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| |
| return; |
| } |
| |
| DVLOG(1) << "ModelAssociationManager: All types have models loaded. " |
| << "Moving on to StartAssociatingNextType."; |
| |
| // If all controllers have their |LoadModels| invoked then pass onto |
| // |StartAssociatingNextType|. |
| StartAssociatingNextType(); |
| } |
| |
| void ModelAssociationManager::ModelLoadCallback( |
| syncer::ModelType type, syncer::SyncError error) { |
| DVLOG(1) << "ModelAssociationManager: ModelLoadCallback for " |
| << syncer::ModelTypeToString(type); |
| if (state_ == CONFIGURING) { |
| DVLOG(1) << "ModelAssociationManager: ModelLoadCallback while configuring"; |
| for (std::vector<DataTypeController*>::iterator it = |
| pending_model_load_.begin(); |
| it != pending_model_load_.end(); |
| ++it) { |
| if ((*it)->type() == type) { |
| // Each type is given |kDataTypeLoadWaitTimeInSeconds| time to load |
| // (as controlled by the timer.). If the type does not load in that |
| // time we move on to the next type. However if the type does |
| // finish loading in that time we want to stop the timer. We stop |
| // the timer, if the type that loaded is the same as the type that |
| // we started the timer for(as indicated by the type on the head |
| // of the list). |
| // Note: Regardless of this timer value the associations will always |
| // take place serially. The only thing this timer controls is how serial |
| // the model load is. If this timer has a value of zero seconds then |
| // the model loads will all be parallel. |
| if (it == pending_model_load_.begin()) { |
| DVLOG(1) << "ModelAssociationManager: Stopping timer"; |
| timer_.Stop(); |
| } |
| DataTypeController* dtc = *it; |
| pending_model_load_.erase(it); |
| if (!error.IsSet()) { |
| DVLOG(1) << "ModelAssociationManager:" |
| << " Calling StartAssociatingNextType"; |
| waiting_to_associate_.push_back(dtc); |
| StartAssociatingNextType(); |
| } else { |
| DVLOG(1) << "ModelAssociationManager: Encountered error loading"; |
| syncer::SyncMergeResult local_merge_result(type); |
| local_merge_result.set_error(error); |
| TypeStartCallback(DataTypeController::ASSOCIATION_FAILED, |
| local_merge_result, |
| syncer::SyncMergeResult(type)); |
| } |
| return; |
| } |
| } |
| NOTREACHED(); |
| return; |
| } else if (state_ == IDLE) { |
| DVLOG(1) << "ModelAssociationManager: Models loaded after configure cycle. " |
| << "Informing DTM"; |
| // This datatype finished loading after the deadline imposed by the |
| // originating configuration cycle. Inform the DataTypeManager that the |
| // type has loaded, so that association may begin. |
| result_processor_->OnTypesLoaded(); |
| } else { |
| // If we're not IDLE or CONFIGURING, we're being invoked as part of an abort |
| // process (possibly a reconfiguration, or disabling of a broken data type). |
| DVLOG(1) << "ModelAssociationManager: ModelLoadCallback occurred while " |
| << "not IDLE or CONFIGURING. Doing nothing."; |
| } |
| |
| } |
| |
| void ModelAssociationManager::StartAssociatingNextType() { |
| DCHECK_EQ(state_, CONFIGURING); |
| DCHECK_EQ(currently_associating_, static_cast<DataTypeController*>(NULL)); |
| |
| DVLOG(1) << "ModelAssociationManager: StartAssociatingNextType"; |
| if (!waiting_to_associate_.empty()) { |
| DVLOG(1) << "ModelAssociationManager: Starting " |
| << waiting_to_associate_[0]->name(); |
| DataTypeController* dtc = waiting_to_associate_[0]; |
| waiting_to_associate_.erase(waiting_to_associate_.begin()); |
| currently_associating_ = dtc; |
| current_type_association_start_time_ = base::Time::Now(); |
| TRACE_EVENT_ASYNC_BEGIN1("sync", "ModelAssociation", |
| currently_associating_, |
| "DataType", |
| ModelTypeToString(currently_associating_->type())); |
| dtc->StartAssociating(base::Bind( |
| &ModelAssociationManager::TypeStartCallback, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // We are done with this cycle of association. Stop any failed types now. |
| needs_stop_.clear(); |
| for (DataTypeController::TypeMap::const_iterator it = controllers_->begin(); |
| it != controllers_->end(); ++it) { |
| DataTypeController* dtc = (*it).second.get(); |
| if (failed_data_types_info_.count(dtc->type()) > 0 && |
| dtc->state() != DataTypeController::NOT_RUNNING) { |
| needs_stop_.push_back(dtc); |
| DVLOG(1) << "ModelTypeToString: Will stop " << dtc->name(); |
| } |
| } |
| StopDisabledTypes(); |
| |
| state_ = IDLE; |
| |
| DataTypeManager::ConfigureStatus configure_status = DataTypeManager::OK; |
| |
| if (!failed_data_types_info_.empty() || |
| !GetTypesWaitingToLoad().Empty() || |
| !needs_crypto_types_.Empty()) { |
| // We have not configured all types that we have been asked to configure. |
| // Either we have failed types or types that have not completed loading |
| // yet. |
| DVLOG(1) << "ModelAssociationManager: setting partial success"; |
| configure_status = DataTypeManager::PARTIAL_SUCCESS; |
| } |
| |
| DataTypeManager::ConfigureResult result(configure_status, |
| associating_types_, |
| failed_data_types_info_, |
| GetTypesWaitingToLoad(), |
| needs_crypto_types_); |
| result_processor_->OnModelAssociationDone(result); |
| return; |
| } |
| |
| syncer::ModelTypeSet ModelAssociationManager::GetTypesWaitingToLoad() { |
| syncer::ModelTypeSet result; |
| for (std::vector<DataTypeController*>::const_iterator it = |
| pending_model_load_.begin(); |
| it != pending_model_load_.end(); |
| ++it) { |
| result.Put((*it)->type()); |
| } |
| return result; |
| } |
| |
| base::OneShotTimer<ModelAssociationManager>* |
| ModelAssociationManager::GetTimerForTesting() { |
| return &timer_; |
| } |
| |
| } // namespace browser_sync |