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