summaryrefslogtreecommitdiffstats
path: root/components/sync_driver/data_type_manager_impl.cc
blob: 91e81fed5776459c010dac620819e04931af02a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
// Copyright 2014 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 "components/sync_driver/data_type_manager_impl.h"

#include <algorithm>
#include <functional>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/profiler/scoped_tracker.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "components/sync_driver/data_type_controller.h"
#include "components/sync_driver/data_type_encryption_handler.h"
#include "components/sync_driver/data_type_manager_observer.h"
#include "components/sync_driver/data_type_status_table.h"
#include "sync/internal_api/public/data_type_debug_info_listener.h"

namespace sync_driver {

namespace {

DataTypeStatusTable::TypeErrorMap
GenerateCryptoErrorsForTypes(syncer::ModelTypeSet encrypted_types) {
  DataTypeStatusTable::TypeErrorMap crypto_errors;
  for (syncer::ModelTypeSet::Iterator iter = encrypted_types.First();
         iter.Good(); iter.Inc()) {
    crypto_errors[iter.Get()] = syncer::SyncError(
        FROM_HERE,
        syncer::SyncError::CRYPTO_ERROR,
        "",
        iter.Get());
  }
  return crypto_errors;
}

}  // namespace

DataTypeManagerImpl::AssociationTypesInfo::AssociationTypesInfo() {}
DataTypeManagerImpl::AssociationTypesInfo::~AssociationTypesInfo() {}

DataTypeManagerImpl::DataTypeManagerImpl(
    const base::Closure& unrecoverable_error_method,
    const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>&
        debug_info_listener,
    const DataTypeController::TypeMap* controllers,
    const DataTypeEncryptionHandler* encryption_handler,
    BackendDataTypeConfigurer* configurer,
    DataTypeManagerObserver* observer)
    : configurer_(configurer),
      controllers_(controllers),
      state_(DataTypeManager::STOPPED),
      needs_reconfigure_(false),
      last_configure_reason_(syncer::CONFIGURE_REASON_UNKNOWN),
      debug_info_listener_(debug_info_listener),
      model_association_manager_(controllers, this),
      observer_(observer),
      encryption_handler_(encryption_handler),
      unrecoverable_error_method_(unrecoverable_error_method),
      catch_up_in_progress_(false),
      weak_ptr_factory_(this) {
  DCHECK(configurer_);
  DCHECK(observer_);
}

DataTypeManagerImpl::~DataTypeManagerImpl() {}

void DataTypeManagerImpl::Configure(syncer::ModelTypeSet desired_types,
                                    syncer::ConfigureReason reason) {
  // Once requested, we will remain in "catch up" mode until we notify the
  // caller (see NotifyDone). We do this to ensure that once started, subsequent
  // calls to Configure won't take us out of "catch up" mode.
  if (reason == syncer::CONFIGURE_REASON_CATCH_UP)
    catch_up_in_progress_ = true;

  if (reason == syncer::CONFIGURE_REASON_BACKUP_ROLLBACK)
    desired_types.PutAll(syncer::ControlTypes());
  else
    desired_types.PutAll(syncer::CoreTypes());

  // Only allow control types and types that have controllers.
  syncer::ModelTypeSet filtered_desired_types;
  for (syncer::ModelTypeSet::Iterator type = desired_types.First();
       type.Good(); type.Inc()) {
    DataTypeController::TypeMap::const_iterator iter =
        controllers_->find(type.Get());
    if (syncer::IsControlType(type.Get()) || iter != controllers_->end()) {
      if (iter != controllers_->end()) {
        if (!iter->second->ReadyForStart() &&
            !data_type_status_table_.GetUnreadyErrorTypes().Has(
                type.Get())) {
          // Add the type to the unready types set to prevent purging it. It's
          // up to the datatype controller to, if necessary, explicitly
          // mark the type as broken to trigger a purge.
          syncer::SyncError error(FROM_HERE,
                                  syncer::SyncError::UNREADY_ERROR,
                                  "Datatype not ready at config time.",
                                  type.Get());
          std::map<syncer::ModelType, syncer::SyncError> errors;
          errors[type.Get()] = error;
          data_type_status_table_.UpdateFailedDataTypes(errors);
        } else if (iter->second->ReadyForStart()) {
          data_type_status_table_.ResetUnreadyErrorFor(type.Get());
        }
      }
      filtered_desired_types.Put(type.Get());
    }
  }
  ConfigureImpl(filtered_desired_types, reason);
}

void DataTypeManagerImpl::ReenableType(syncer::ModelType type) {
  // TODO(zea): move the "should we reconfigure" logic into the datatype handler
  // itself.
  // Only reconfigure if the type actually had a data type or unready error.
  if (!data_type_status_table_.ResetDataTypeErrorFor(type) &&
      !data_type_status_table_.ResetUnreadyErrorFor(type)) {
    return;
  }

  // Only reconfigure if the type is actually desired.
  if (!last_requested_types_.Has(type))
    return;

  DVLOG(1) << "Reenabling " << syncer::ModelTypeToString(type);
  needs_reconfigure_ = true;
  last_configure_reason_ = syncer::CONFIGURE_REASON_PROGRAMMATIC;
  ProcessReconfigure();
}

void DataTypeManagerImpl::ResetDataTypeErrors() {
  data_type_status_table_.Reset();
}

void DataTypeManagerImpl::PurgeForMigration(
    syncer::ModelTypeSet undesired_types,
    syncer::ConfigureReason reason) {
  syncer::ModelTypeSet remainder = Difference(last_requested_types_,
                                              undesired_types);
  ConfigureImpl(remainder, reason);
}

void DataTypeManagerImpl::ConfigureImpl(
    syncer::ModelTypeSet desired_types,
    syncer::ConfigureReason reason) {
  DCHECK_NE(reason, syncer::CONFIGURE_REASON_UNKNOWN);
  DVLOG(1) << "Configuring for " << syncer::ModelTypeSetToString(desired_types)
           << " with reason " << reason;
  if (state_ == STOPPING) {
    // You can not set a configuration while stopping.
    LOG(ERROR) << "Configuration set while stopping.";
    return;
  }

  // TODO(zea): consider not performing a full configuration once there's a
  // reliable way to determine if the requested set of enabled types matches the
  // current set.

  last_requested_types_ = desired_types;
  last_configure_reason_ = reason;
  // Only proceed if we're in a steady state or retrying.
  if (state_ != STOPPED && state_ != CONFIGURED && state_ != RETRYING) {
    DVLOG(1) << "Received configure request while configuration in flight. "
             << "Postponing until current configuration complete.";
    needs_reconfigure_ = true;
    return;
  }

  Restart(reason);
}

BackendDataTypeConfigurer::DataTypeConfigStateMap
DataTypeManagerImpl::BuildDataTypeConfigStateMap(
    const syncer::ModelTypeSet& types_being_configured) const {
  // 1. Get the failed types (due to fatal, crypto, and unready errors).
  // 2. Add the difference between last_requested_types_ and the failed types
  //    as CONFIGURE_INACTIVE.
  // 3. Flip |types_being_configured| to CONFIGURE_ACTIVE.
  // 4. Set non-enabled user types as DISABLED.
  // 5. Set the fatal, crypto, and unready types to their respective states.
  syncer::ModelTypeSet error_types =
      data_type_status_table_.GetFailedTypes();
  syncer::ModelTypeSet fatal_types =
      data_type_status_table_.GetFatalErrorTypes();
  syncer::ModelTypeSet crypto_types =
      data_type_status_table_.GetCryptoErrorTypes();
  syncer::ModelTypeSet unready_types=
      data_type_status_table_.GetUnreadyErrorTypes();

  // Types with persistence errors are only purged/resynced when they're
  // actively being configured.
  syncer::ModelTypeSet clean_types =
      data_type_status_table_.GetPersistenceErrorTypes();
  clean_types.RetainAll(types_being_configured);

  // Types with unready errors do not count as unready if they've been disabled.
  unready_types.RetainAll(last_requested_types_);

  syncer::ModelTypeSet enabled_types = last_requested_types_;
  enabled_types.RemoveAll(error_types);

  // If we're catching up, add all enabled (non-error) types to the clean set to
  // ensure we download and apply them to the model types.
  if (catch_up_in_progress_)
    clean_types.PutAll(enabled_types);

  syncer::ModelTypeSet disabled_types =
      syncer::Difference(
          syncer::Union(syncer::UserTypes(), syncer::ControlTypes()),
          enabled_types);
  syncer::ModelTypeSet to_configure = syncer::Intersection(
      enabled_types, types_being_configured);
  DVLOG(1) << "Enabling: " << syncer::ModelTypeSetToString(enabled_types);
  DVLOG(1) << "Configuring: " << syncer::ModelTypeSetToString(to_configure);
  DVLOG(1) << "Disabling: " << syncer::ModelTypeSetToString(disabled_types);

  BackendDataTypeConfigurer::DataTypeConfigStateMap config_state_map;
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::CONFIGURE_INACTIVE, enabled_types,
      &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::CONFIGURE_ACTIVE, to_configure,
      &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::CONFIGURE_CLEAN, clean_types,
        &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::DISABLED, disabled_types,
      &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::FATAL, fatal_types,
      &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::CRYPTO, crypto_types,
        &config_state_map);
  BackendDataTypeConfigurer::SetDataTypesState(
      BackendDataTypeConfigurer::UNREADY, unready_types,
        &config_state_map);
  return config_state_map;
}

void DataTypeManagerImpl::Restart(syncer::ConfigureReason reason) {
  DVLOG(1) << "Restarting...";

  // Only record the type histograms for user-triggered configurations or
  // restarts.
  if (reason == syncer::CONFIGURE_REASON_RECONFIGURATION ||
      reason == syncer::CONFIGURE_REASON_NEW_CLIENT ||
      reason == syncer::CONFIGURE_REASON_NEWLY_ENABLED_DATA_TYPE) {
    for (syncer::ModelTypeSet::Iterator iter = last_requested_types_.First();
         iter.Good(); iter.Inc()) {
      UMA_HISTOGRAM_ENUMERATION("Sync.ConfigureDataTypes",
                                syncer::ModelTypeToHistogramInt(iter.Get()),
                                syncer::MODEL_TYPE_COUNT);
    }
  }

  // Check for new or resolved data type crypto errors.
  if (encryption_handler_->IsPassphraseRequired()) {
    syncer::ModelTypeSet encrypted_types =
        encryption_handler_->GetEncryptedDataTypes();
    encrypted_types.RetainAll(last_requested_types_);
    encrypted_types.RemoveAll(
        data_type_status_table_.GetCryptoErrorTypes());
    DataTypeStatusTable::TypeErrorMap crypto_errors =
        GenerateCryptoErrorsForTypes(encrypted_types);
    data_type_status_table_.UpdateFailedDataTypes(crypto_errors);
  } else {
    data_type_status_table_.ResetCryptoErrors();
  }

  syncer::ModelTypeSet failed_types =
      data_type_status_table_.GetFailedTypes();
  syncer::ModelTypeSet enabled_types =
      syncer::Difference(last_requested_types_, failed_types);

  last_restart_time_ = base::Time::Now();
  configuration_stats_.clear();

  DCHECK(state_ == STOPPED || state_ == CONFIGURED || state_ == RETRYING);

  const State old_state = state_;
  state_ = CONFIGURING;

  // Starting from a "steady state" (stopped or configured) state
  // should send a start notification.
  // Note: NotifyStart() must be called with the updated (non-idle) state,
  // otherwise logic listening for the configuration start might not be aware
  // of the fact that the DTM is in a configuration state.
  if (old_state == STOPPED || old_state == CONFIGURED)
    NotifyStart();

  download_types_queue_ = PrioritizeTypes(enabled_types);
  association_types_queue_ = std::queue<AssociationTypesInfo>();

  // If we're performing a "catch up", first stop the model types to ensure the
  // call to Initialize triggers model association.
  if (catch_up_in_progress_)
    model_association_manager_.Stop();
  model_association_manager_.Initialize(enabled_types);

  StartNextDownload(syncer::ModelTypeSet());
}

syncer::ModelTypeSet DataTypeManagerImpl::GetPriorityTypes() const {
  syncer::ModelTypeSet high_priority_types;
  high_priority_types.PutAll(syncer::PriorityCoreTypes());
  high_priority_types.PutAll(syncer::PriorityUserTypes());
  return high_priority_types;
}

TypeSetPriorityList DataTypeManagerImpl::PrioritizeTypes(
    const syncer::ModelTypeSet& types) {
  syncer::ModelTypeSet high_priority_types = GetPriorityTypes();
  high_priority_types.RetainAll(types);

  syncer::ModelTypeSet low_priority_types =
      syncer::Difference(types, high_priority_types);

  TypeSetPriorityList result;
  if (!high_priority_types.Empty())
    result.push(high_priority_types);
  if (!low_priority_types.Empty())
    result.push(low_priority_types);

  // Could be empty in case of purging for migration, sync nothing, etc.
  // Configure empty set to purge data from backend.
  if (result.empty())
    result.push(syncer::ModelTypeSet());

  return result;
}

void DataTypeManagerImpl::ProcessReconfigure() {
  DCHECK(needs_reconfigure_);

  // Wait for current download and association to finish.
  if (!download_types_queue_.empty() ||
      model_association_manager_.state() ==
          ModelAssociationManager::ASSOCIATING) {
    return;
  }

  association_types_queue_ = std::queue<AssociationTypesInfo>();

  // An attempt was made to reconfigure while we were already configuring.
  // This can be because a passphrase was accepted or the user changed the
  // set of desired types. Either way, |last_requested_types_| will contain
  // the most recent set of desired types, so we just call configure.
  // Note: we do this whether or not GetControllersNeedingStart is true,
  // because we may need to stop datatypes.
  DVLOG(1) << "Reconfiguring due to previous configure attempt occuring while"
           << " busy.";

  // Note: ConfigureImpl is called directly, rather than posted, in order to
  // ensure that any purging/unapplying/journaling happens while the set of
  // failed types is still up to date. If stack unwinding were to be done
  // via PostTask, the failed data types may be reset before the purging was
  // performed.
  state_ = RETRYING;
  needs_reconfigure_ = false;
  ConfigureImpl(last_requested_types_, last_configure_reason_);
}

void DataTypeManagerImpl::OnDownloadRetry() {
  DCHECK_EQ(CONFIGURING, state_);
}

void DataTypeManagerImpl::DownloadReady(
    syncer::ModelTypeSet types_to_download,
    syncer::ModelTypeSet first_sync_types,
    syncer::ModelTypeSet failed_configuration_types) {
  DCHECK_EQ(CONFIGURING, state_);

  // Persistence errors are reset after each backend configuration attempt
  // during which they would have been purged.
  data_type_status_table_.ResetPersistenceErrorsFrom(types_to_download);

  if (!failed_configuration_types.Empty()) {
    DataTypeStatusTable::TypeErrorMap errors;
    for (syncer::ModelTypeSet::Iterator iter =
             failed_configuration_types.First(); iter.Good(); iter.Inc()) {
      syncer::SyncError error(
          FROM_HERE,
          syncer::SyncError::DATATYPE_ERROR,
          "Backend failed to download type.",
          iter.Get());
      errors[iter.Get()] = error;
    }
    data_type_status_table_.UpdateFailedDataTypes(errors);
    needs_reconfigure_ = true;
  }

  if (needs_reconfigure_) {
    download_types_queue_ = TypeSetPriorityList();
    ProcessReconfigure();
    return;
  }

  CHECK(!download_types_queue_.empty());
  download_types_queue_.pop();

  // Those types that were already downloaded (non first sync/error types)
  // should already be associating. Just kick off association of the newly
  // downloaded types if necessary.
  if (!association_types_queue_.empty()) {
    association_types_queue_.back().first_sync_types = first_sync_types;
    association_types_queue_.back().download_ready_time = base::Time::Now();
    StartNextAssociation(UNREADY_AT_CONFIG);
  } else if (download_types_queue_.empty() &&
             model_association_manager_.state() !=
                 ModelAssociationManager::ASSOCIATING) {
    // There's nothing more to download or associate (implying either there were
    // no types to associate or they associated as part of |ready_types|).
    // If the model association manager is also finished, then we're done
    // configuring.
    state_ = CONFIGURED;
    ConfigureResult result(OK, last_requested_types_);
    NotifyDone(result);
    return;
  }

  StartNextDownload(types_to_download);
}

void DataTypeManagerImpl::StartNextDownload(
    syncer::ModelTypeSet high_priority_types_before) {
  if (download_types_queue_.empty())
    return;

  // Tell the backend about the new set of data types we wish to sync.
  // The task will be invoked when updates are downloaded.
  syncer::ModelTypeSet ready_types = configurer_->ConfigureDataTypes(
      last_configure_reason_,
      BuildDataTypeConfigStateMap(download_types_queue_.front()),
      base::Bind(&DataTypeManagerImpl::DownloadReady,
                 weak_ptr_factory_.GetWeakPtr(),
                 download_types_queue_.front()),
      base::Bind(&DataTypeManagerImpl::OnDownloadRetry,
                 weak_ptr_factory_.GetWeakPtr()));

  AssociationTypesInfo association_info;
  association_info.types = download_types_queue_.front();
  association_info.ready_types = ready_types;
  association_info.download_start_time = base::Time::Now();
  association_info.high_priority_types_before = high_priority_types_before;
  association_types_queue_.push(association_info);

  // Start associating those types that are already downloaded (does nothing
  // if model associator is busy).
  StartNextAssociation(READY_AT_CONFIG);
}

void DataTypeManagerImpl::StartNextAssociation(AssociationGroup group) {
  CHECK(!association_types_queue_.empty());

  // If the model association manager is already associating, let it finish.
  // The model association done event will result in associating any remaining
  // association groups.
  if (model_association_manager_.state() !=
          ModelAssociationManager::INITIALIZED) {
    return;
  }

  syncer::ModelTypeSet types_to_associate;
  if (group == READY_AT_CONFIG) {
    association_types_queue_.front().ready_association_request_time =
        base::Time::Now();
    types_to_associate = association_types_queue_.front().ready_types;
  } else {
    DCHECK_EQ(UNREADY_AT_CONFIG, group);
    // Only start associating the rest of the types if they have all finished
    // downloading.
    if (association_types_queue_.front().download_ready_time.is_null())
      return;
    association_types_queue_.front().full_association_request_time =
        base::Time::Now();
    // We request the full set of types here for completeness sake. All types
    // within the READY_AT_CONFIG set will already be started and should be
    // no-ops.
    types_to_associate = association_types_queue_.front().types;
  }


  DVLOG(1) << "Associating "
           << syncer::ModelTypeSetToString(types_to_associate);
  model_association_manager_.StartAssociationAsync(types_to_associate);
}

void DataTypeManagerImpl::OnSingleDataTypeWillStop(
    syncer::ModelType type,
    const syncer::SyncError& error) {
  configurer_->DeactivateDataType(type);
  if (error.IsSet()) {
    DataTypeStatusTable::TypeErrorMap failed_types;
    failed_types[type] = error;
    data_type_status_table_.UpdateFailedDataTypes(
            failed_types);

    // Unrecoverable errors will shut down the entire backend, so no need to
    // reconfigure.
    if (error.error_type() != syncer::SyncError::UNRECOVERABLE_ERROR) {
      needs_reconfigure_ = true;
      last_configure_reason_ = syncer::CONFIGURE_REASON_PROGRAMMATIC;
      ProcessReconfigure();
    }
  }
}

void DataTypeManagerImpl::OnSingleDataTypeAssociationDone(
    syncer::ModelType type,
    const syncer::DataTypeAssociationStats& association_stats) {
  DCHECK(!association_types_queue_.empty());
  DataTypeController::TypeMap::const_iterator c_it = controllers_->find(type);
  DCHECK(c_it != controllers_->end());
  if (c_it->second->state() == DataTypeController::RUNNING) {
    // Tell the backend about the change processor for this type so it can
    // begin routing changes to it.
    configurer_->ActivateDataType(type, c_it->second->model_safe_group(),
                                  c_it->second->GetChangeProcessor());
  }

  if (!debug_info_listener_.IsInitialized())
    return;

  AssociationTypesInfo& info = association_types_queue_.front();
  configuration_stats_.push_back(syncer::DataTypeConfigurationStats());
  configuration_stats_.back().model_type = type;
  configuration_stats_.back().association_stats = association_stats;
  if (info.types.Has(type)) {
    // Times in |info| only apply to non-slow types.
    configuration_stats_.back().download_wait_time =
        info.download_start_time - last_restart_time_;
    if (info.first_sync_types.Has(type)) {
      configuration_stats_.back().download_time =
          info.download_ready_time - info.download_start_time;
    }
    if (info.ready_types.Has(type)) {
      configuration_stats_.back().association_wait_time_for_high_priority =
          info.ready_association_request_time - info.download_start_time;
    } else {
      configuration_stats_.back().association_wait_time_for_high_priority =
          info.full_association_request_time - info.download_ready_time;
    }
    configuration_stats_.back().high_priority_types_configured_before =
        info.high_priority_types_before;
    configuration_stats_.back().same_priority_types_configured_before =
        info.configured_types;
    info.configured_types.Put(type);
  }
}

void DataTypeManagerImpl::OnModelAssociationDone(
    const DataTypeManager::ConfigureResult& result) {
  DCHECK(state_ == STOPPING || state_ == CONFIGURING);

  if (state_ == STOPPING)
    return;

  // Ignore abort/unrecoverable error if we need to reconfigure anyways.
  if (needs_reconfigure_) {
    ProcessReconfigure();
    return;
  }

  if (result.status == ABORTED || result.status == UNRECOVERABLE_ERROR) {
    Abort(result.status);
    return;
  }

  DCHECK(result.status == OK);

  CHECK(!association_types_queue_.empty());

  // If this model association was for the full set of types, then this priority
  // set is done. Otherwise it was just the ready types and the unready types
  // still need to be associated.
  if (result.requested_types.Equals(association_types_queue_.front().types)) {
    association_types_queue_.pop();
    if (!association_types_queue_.empty()) {
      StartNextAssociation(READY_AT_CONFIG);
    } else if (download_types_queue_.empty()) {
      state_ = CONFIGURED;
      NotifyDone(result);
    }
  } else {
    DCHECK(result.requested_types.Equals(
        association_types_queue_.front().ready_types));
    // Will do nothing if the types are still downloading.
    StartNextAssociation(UNREADY_AT_CONFIG);
  }
}

void DataTypeManagerImpl::Stop() {
  if (state_ == STOPPED)
    return;

  bool need_to_notify = state_ == CONFIGURING;
  StopImpl();

  if (need_to_notify) {
    ConfigureResult result(ABORTED,
                           last_requested_types_);
    NotifyDone(result);
  }
}

void DataTypeManagerImpl::Abort(ConfigureStatus status) {
  DCHECK_EQ(CONFIGURING, state_);

  StopImpl();

  DCHECK_NE(OK, status);
  ConfigureResult result(status,
                         last_requested_types_);
  NotifyDone(result);
}

void DataTypeManagerImpl::StopImpl() {
  state_ = STOPPING;

  // Invalidate weak pointer to drop download callbacks.
  weak_ptr_factory_.InvalidateWeakPtrs();

  // Stop all data types. This may trigger association callback but the
  // callback will do nothing because state is set to STOPPING above.
  model_association_manager_.Stop();

  state_ = STOPPED;
}

void DataTypeManagerImpl::NotifyStart() {
  observer_->OnConfigureStart();
}

void DataTypeManagerImpl::NotifyDone(const ConfigureResult& raw_result) {
  catch_up_in_progress_ = false;

  AddToConfigureTime();

  ConfigureResult result = raw_result;
  result.data_type_status_table = data_type_status_table_;

  DVLOG(1) << "Total time spent configuring: "
           << configure_time_delta_.InSecondsF() << "s";
  switch (result.status) {
    case DataTypeManager::OK:
      DVLOG(1) << "NotifyDone called with result: OK";
      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.OK",
                               configure_time_delta_);
      if (debug_info_listener_.IsInitialized() &&
          !configuration_stats_.empty()) {
        debug_info_listener_.Call(
            FROM_HERE,
            &syncer::DataTypeDebugInfoListener::OnDataTypeConfigureComplete,
            configuration_stats_);
      }
      configuration_stats_.clear();
      break;
    case DataTypeManager::ABORTED:
      DVLOG(1) << "NotifyDone called with result: ABORTED";
      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.ABORTED",
                               configure_time_delta_);
      break;
    case DataTypeManager::UNRECOVERABLE_ERROR:
      DVLOG(1) << "NotifyDone called with result: UNRECOVERABLE_ERROR";
      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.UNRECOVERABLE_ERROR",
                               configure_time_delta_);
      break;
    case DataTypeManager::UNKNOWN:
      NOTREACHED();
      break;
  }
  observer_->OnConfigureDone(result);
}

DataTypeManager::State DataTypeManagerImpl::state() const {
  return state_;
}

void DataTypeManagerImpl::AddToConfigureTime() {
  DCHECK(!last_restart_time_.is_null());
  configure_time_delta_ += (base::Time::Now() - last_restart_time_);
}

}  // namespace sync_driver