// 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::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