1// Copyright 2014 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "components/sync_driver/non_ui_data_type_controller.h" 6 7#include "base/logging.h" 8#include "base/memory/weak_ptr.h" 9#include "components/sync_driver/generic_change_processor_factory.h" 10#include "components/sync_driver/shared_change_processor_ref.h" 11#include "components/sync_driver/sync_api_component_factory.h" 12#include "sync/api/sync_error.h" 13#include "sync/api/syncable_service.h" 14#include "sync/internal_api/public/base/model_type.h" 15#include "sync/util/data_type_histogram.h" 16 17namespace sync_driver { 18 19SharedChangeProcessor* 20NonUIDataTypeController::CreateSharedChangeProcessor() { 21 return new SharedChangeProcessor(); 22} 23 24NonUIDataTypeController::NonUIDataTypeController( 25 scoped_refptr<base::MessageLoopProxy> ui_thread, 26 const base::Closure& error_callback, 27 SyncApiComponentFactory* sync_factory) 28 : DataTypeController(ui_thread, error_callback), 29 sync_factory_(sync_factory), 30 state_(NOT_RUNNING), 31 ui_thread_(ui_thread) { 32} 33 34void NonUIDataTypeController::LoadModels( 35 const ModelLoadCallback& model_load_callback) { 36 DCHECK(ui_thread_->BelongsToCurrentThread()); 37 model_load_callback_ = model_load_callback; 38 if (state() != NOT_RUNNING) { 39 model_load_callback.Run(type(), 40 syncer::SyncError(FROM_HERE, 41 syncer::SyncError::DATATYPE_ERROR, 42 "Model already running", 43 type())); 44 return; 45 } 46 47 state_ = MODEL_STARTING; 48 // Since we can't be called multiple times before Stop() is called, 49 // |shared_change_processor_| must be NULL here. 50 DCHECK(!shared_change_processor_.get()); 51 shared_change_processor_ = CreateSharedChangeProcessor(); 52 DCHECK(shared_change_processor_.get()); 53 if (!StartModels()) { 54 // If we are waiting for some external service to load before associating 55 // or we failed to start the models, we exit early. 56 DCHECK(state() == MODEL_STARTING || state() == NOT_RUNNING); 57 return; 58 } 59 60 OnModelLoaded(); 61} 62 63void NonUIDataTypeController::OnModelLoaded() { 64 DCHECK_EQ(state_, MODEL_STARTING); 65 state_ = MODEL_LOADED; 66 model_load_callback_.Run(type(), syncer::SyncError()); 67} 68 69bool NonUIDataTypeController::StartModels() { 70 DCHECK_EQ(state_, MODEL_STARTING); 71 // By default, no additional services need to be started before we can proceed 72 // with model association. 73 return true; 74} 75 76void NonUIDataTypeController::StopModels() { 77 DCHECK(ui_thread_->BelongsToCurrentThread()); 78} 79 80void NonUIDataTypeController::StartAssociating( 81 const StartCallback& start_callback) { 82 DCHECK(ui_thread_->BelongsToCurrentThread()); 83 DCHECK(!start_callback.is_null()); 84 DCHECK_EQ(state_, MODEL_LOADED); 85 state_ = ASSOCIATING; 86 87 start_callback_ = start_callback; 88 if (!StartAssociationAsync()) { 89 syncer::SyncError error( 90 FROM_HERE, 91 syncer::SyncError::DATATYPE_ERROR, 92 "Failed to post StartAssociation", 93 type()); 94 syncer::SyncMergeResult local_merge_result(type()); 95 local_merge_result.set_error(error); 96 StartDoneImpl(ASSOCIATION_FAILED, 97 NOT_RUNNING, 98 local_merge_result, 99 syncer::SyncMergeResult(type())); 100 // StartDoneImpl should have called ClearSharedChangeProcessor(); 101 DCHECK(!shared_change_processor_.get()); 102 return; 103 } 104} 105 106void NonUIDataTypeController::Stop() { 107 DCHECK(ui_thread_->BelongsToCurrentThread()); 108 109 if (state() == NOT_RUNNING) 110 return; 111 112 // Disconnect the change processor. At this point, the 113 // syncer::SyncableService can no longer interact with the Syncer, even if 114 // it hasn't finished MergeDataAndStartSyncing. 115 ClearSharedChangeProcessor(); 116 117 // If we haven't finished starting, we need to abort the start. 118 switch (state()) { 119 case MODEL_STARTING: 120 state_ = STOPPING; 121 AbortModelLoad(); 122 return; // The datatype was never activated, we're done. 123 case ASSOCIATING: 124 state_ = STOPPING; 125 // We continue on to deactivate the datatype and stop the local service. 126 break; 127 case MODEL_LOADED: 128 case DISABLED: 129 // If DTC is loaded or disabled, we never attempted or succeeded 130 // associating and never activated the datatype. We would have already 131 // stopped the local service in StartDoneImpl(..). 132 state_ = NOT_RUNNING; 133 StopModels(); 134 return; 135 default: 136 // Datatype was fully started. Need to deactivate and stop the local 137 // service. 138 DCHECK_EQ(state(), RUNNING); 139 state_ = STOPPING; 140 StopModels(); 141 break; 142 } 143 144 // Stop the local service and release our references to it and the 145 // shared change processor (posts a task to the datatype's thread). 146 StopLocalServiceAsync(); 147 148 state_ = NOT_RUNNING; 149} 150 151std::string NonUIDataTypeController::name() const { 152 // For logging only. 153 return syncer::ModelTypeToString(type()); 154} 155 156DataTypeController::State NonUIDataTypeController::state() const { 157 return state_; 158} 159 160void NonUIDataTypeController::OnSingleDataTypeUnrecoverableError( 161 const syncer::SyncError& error) { 162 DCHECK(!ui_thread_->BelongsToCurrentThread()); 163 // TODO(tim): We double-upload some errors. See bug 383480. 164 if (!error_callback_.is_null()) 165 error_callback_.Run(); 166 ui_thread_->PostTask(error.location(), 167 base::Bind(&NonUIDataTypeController::DisableImpl, 168 this, 169 error)); 170} 171 172NonUIDataTypeController::NonUIDataTypeController() 173 : DataTypeController(base::MessageLoopProxy::current(), base::Closure()), 174 sync_factory_(NULL) {} 175 176NonUIDataTypeController::~NonUIDataTypeController() {} 177 178void NonUIDataTypeController::StartDone( 179 DataTypeController::ConfigureResult start_result, 180 const syncer::SyncMergeResult& local_merge_result, 181 const syncer::SyncMergeResult& syncer_merge_result) { 182 DCHECK(!ui_thread_->BelongsToCurrentThread()); 183 184 DataTypeController::State new_state; 185 if (IsSuccessfulResult(start_result)) { 186 new_state = RUNNING; 187 } else { 188 new_state = (start_result == ASSOCIATION_FAILED ? DISABLED : NOT_RUNNING); 189 } 190 191 ui_thread_->PostTask(FROM_HERE, 192 base::Bind(&NonUIDataTypeController::StartDoneImpl, 193 this, 194 start_result, 195 new_state, 196 local_merge_result, 197 syncer_merge_result)); 198} 199 200void NonUIDataTypeController::StartDoneImpl( 201 DataTypeController::ConfigureResult start_result, 202 DataTypeController::State new_state, 203 const syncer::SyncMergeResult& local_merge_result, 204 const syncer::SyncMergeResult& syncer_merge_result) { 205 DCHECK(ui_thread_->BelongsToCurrentThread()); 206 207 // If we failed to start up, and we haven't been stopped yet, we need to 208 // ensure we clean up the local service and shared change processor properly. 209 if (new_state != RUNNING && state() != NOT_RUNNING && state() != STOPPING) { 210 ClearSharedChangeProcessor(); 211 StopLocalServiceAsync(); 212 } 213 214 // It's possible to have StartDoneImpl called first from the UI thread 215 // (due to Stop being called) and then posted from the non-UI thread. In 216 // this case, we drop the second call because we've already been stopped. 217 if (state_ == NOT_RUNNING) { 218 return; 219 } 220 221 state_ = new_state; 222 if (state_ != RUNNING) { 223 // Start failed. 224 StopModels(); 225 RecordStartFailure(start_result); 226 } 227 228 start_callback_.Run(start_result, local_merge_result, syncer_merge_result); 229} 230 231void NonUIDataTypeController::RecordAssociationTime(base::TimeDelta time) { 232 DCHECK(!ui_thread_->BelongsToCurrentThread()); 233#define PER_DATA_TYPE_MACRO(type_str) \ 234 UMA_HISTOGRAM_TIMES("Sync." type_str "AssociationTime", time); 235 SYNC_DATA_TYPE_HISTOGRAM(type()); 236#undef PER_DATA_TYPE_MACRO 237} 238 239void NonUIDataTypeController::RecordStartFailure(ConfigureResult result) { 240 DCHECK(ui_thread_->BelongsToCurrentThread()); 241 UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeStartFailures", 242 ModelTypeToHistogramInt(type()), 243 syncer::MODEL_TYPE_COUNT); 244#define PER_DATA_TYPE_MACRO(type_str) \ 245 UMA_HISTOGRAM_ENUMERATION("Sync." type_str "StartFailure", result, \ 246 MAX_START_RESULT); 247 SYNC_DATA_TYPE_HISTOGRAM(type()); 248#undef PER_DATA_TYPE_MACRO 249} 250 251void NonUIDataTypeController::AbortModelLoad() { 252 state_ = NOT_RUNNING; 253 StopModels(); 254} 255 256void NonUIDataTypeController::DisableImpl( 257 const syncer::SyncError& error) { 258 DCHECK(ui_thread_->BelongsToCurrentThread()); 259 UMA_HISTOGRAM_ENUMERATION("Sync.DataTypeRunFailures", 260 ModelTypeToHistogramInt(type()), 261 syncer::MODEL_TYPE_COUNT); 262 if (!model_load_callback_.is_null()) { 263 syncer::SyncMergeResult local_merge_result(type()); 264 local_merge_result.set_error(error); 265 model_load_callback_.Run(type(), error); 266 } 267} 268 269bool NonUIDataTypeController::StartAssociationAsync() { 270 DCHECK(ui_thread_->BelongsToCurrentThread()); 271 DCHECK_EQ(state(), ASSOCIATING); 272 return PostTaskOnBackendThread( 273 FROM_HERE, 274 base::Bind( 275 &NonUIDataTypeController::StartAssociationWithSharedChangeProcessor, 276 this, 277 shared_change_processor_)); 278} 279 280ChangeProcessor* NonUIDataTypeController::GetChangeProcessor() const { 281 DCHECK_EQ(state_, RUNNING); 282 return shared_change_processor_->generic_change_processor(); 283} 284 285// This method can execute after we've already stopped (and possibly even 286// destroyed) both the Syncer and the SyncableService. As a result, all actions 287// must either have no side effects outside of the DTC or must be protected 288// by |shared_change_processor|, which is guaranteed to have been Disconnected 289// if the syncer shut down. 290void NonUIDataTypeController:: 291 StartAssociationWithSharedChangeProcessor( 292 const scoped_refptr<SharedChangeProcessor>& shared_change_processor) { 293 DCHECK(!ui_thread_->BelongsToCurrentThread()); 294 DCHECK(shared_change_processor.get()); 295 syncer::SyncMergeResult local_merge_result(type()); 296 syncer::SyncMergeResult syncer_merge_result(type()); 297 base::WeakPtrFactory<syncer::SyncMergeResult> weak_ptr_factory( 298 &syncer_merge_result); 299 300 // Connect |shared_change_processor| to the syncer and get the 301 // syncer::SyncableService associated with type(). 302 // Note that it's possible the shared_change_processor has already been 303 // disconnected at this point, so all our accesses to the syncer from this 304 // point on are through it. 305 GenericChangeProcessorFactory factory; 306 local_service_ = shared_change_processor->Connect( 307 sync_factory_, 308 &factory, 309 user_share(), 310 this, 311 type(), 312 weak_ptr_factory.GetWeakPtr()); 313 if (!local_service_.get()) { 314 syncer::SyncError error(FROM_HERE, 315 syncer::SyncError::DATATYPE_ERROR, 316 "Failed to connect to syncer.", 317 type()); 318 local_merge_result.set_error(error); 319 StartDone(ASSOCIATION_FAILED, 320 local_merge_result, 321 syncer_merge_result); 322 return; 323 } 324 325 if (!shared_change_processor->CryptoReadyIfNecessary()) { 326 syncer::SyncError error(FROM_HERE, 327 syncer::SyncError::CRYPTO_ERROR, 328 "", 329 type()); 330 local_merge_result.set_error(error); 331 StartDone(NEEDS_CRYPTO, 332 local_merge_result, 333 syncer_merge_result); 334 return; 335 } 336 337 bool sync_has_nodes = false; 338 if (!shared_change_processor->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { 339 syncer::SyncError error(FROM_HERE, 340 syncer::SyncError::UNRECOVERABLE_ERROR, 341 "Failed to load sync nodes", 342 type()); 343 local_merge_result.set_error(error); 344 StartDone(UNRECOVERABLE_ERROR, 345 local_merge_result, 346 syncer_merge_result); 347 return; 348 } 349 350 base::TimeTicks start_time = base::TimeTicks::Now(); 351 syncer::SyncDataList initial_sync_data; 352 syncer::SyncError error = 353 shared_change_processor->GetAllSyncDataReturnError( 354 type(), &initial_sync_data); 355 if (error.IsSet()) { 356 local_merge_result.set_error(error); 357 StartDone(ASSOCIATION_FAILED, 358 local_merge_result, 359 syncer_merge_result); 360 return; 361 } 362 363 std::string datatype_context; 364 if (shared_change_processor->GetDataTypeContext(&datatype_context)) { 365 local_service_->UpdateDataTypeContext( 366 type(), syncer::SyncChangeProcessor::NO_REFRESH, datatype_context); 367 } 368 369 syncer_merge_result.set_num_items_before_association( 370 initial_sync_data.size()); 371 // Passes a reference to |shared_change_processor|. 372 local_merge_result = 373 local_service_->MergeDataAndStartSyncing( 374 type(), 375 initial_sync_data, 376 scoped_ptr<syncer::SyncChangeProcessor>( 377 new SharedChangeProcessorRef(shared_change_processor)), 378 scoped_ptr<syncer::SyncErrorFactory>( 379 new SharedChangeProcessorRef(shared_change_processor))); 380 RecordAssociationTime(base::TimeTicks::Now() - start_time); 381 if (local_merge_result.error().IsSet()) { 382 StartDone(ASSOCIATION_FAILED, 383 local_merge_result, 384 syncer_merge_result); 385 return; 386 } 387 388 syncer_merge_result.set_num_items_after_association( 389 shared_change_processor->GetSyncCount()); 390 391 StartDone(!sync_has_nodes ? OK_FIRST_RUN : OK, 392 local_merge_result, 393 syncer_merge_result); 394} 395 396void NonUIDataTypeController::ClearSharedChangeProcessor() { 397 DCHECK(ui_thread_->BelongsToCurrentThread()); 398 // |shared_change_processor_| can already be NULL if Stop() is 399 // called after StartDoneImpl(_, DISABLED, _). 400 if (shared_change_processor_.get()) { 401 shared_change_processor_->Disconnect(); 402 shared_change_processor_ = NULL; 403 } 404} 405 406void NonUIDataTypeController::StopLocalServiceAsync() { 407 DCHECK(ui_thread_->BelongsToCurrentThread()); 408 PostTaskOnBackendThread( 409 FROM_HERE, 410 base::Bind(&NonUIDataTypeController::StopLocalService, this)); 411} 412 413void NonUIDataTypeController::StopLocalService() { 414 DCHECK(!ui_thread_->BelongsToCurrentThread()); 415 if (local_service_.get()) 416 local_service_->StopSyncing(type()); 417 local_service_.reset(); 418} 419 420} // namespace sync_driver 421