blob: 1dbdc85653c417e8fb59dfc88e553882172d2894 [file] [log] [blame]
// 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