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