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/data_type_manager_impl.h"
6
7#include <algorithm>
8#include <functional>
9
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/callback.h"
13#include "base/compiler_specific.h"
14#include "base/debug/trace_event.h"
15#include "base/logging.h"
16#include "base/metrics/histogram.h"
17#include "base/strings/stringprintf.h"
18#include "components/sync_driver/data_type_controller.h"
19#include "components/sync_driver/data_type_encryption_handler.h"
20#include "components/sync_driver/data_type_manager_observer.h"
21#include "components/sync_driver/data_type_status_table.h"
22#include "sync/internal_api/public/data_type_debug_info_listener.h"
23
24namespace sync_driver {
25
26namespace {
27
28DataTypeStatusTable::TypeErrorMap
29GenerateCryptoErrorsForTypes(syncer::ModelTypeSet encrypted_types) {
30  DataTypeStatusTable::TypeErrorMap crypto_errors;
31  for (syncer::ModelTypeSet::Iterator iter = encrypted_types.First();
32         iter.Good(); iter.Inc()) {
33    crypto_errors[iter.Get()] = syncer::SyncError(
34        FROM_HERE,
35        syncer::SyncError::CRYPTO_ERROR,
36        "",
37        iter.Get());
38  }
39  return crypto_errors;
40}
41
42}  // namespace
43
44DataTypeManagerImpl::AssociationTypesInfo::AssociationTypesInfo() {}
45DataTypeManagerImpl::AssociationTypesInfo::~AssociationTypesInfo() {}
46
47DataTypeManagerImpl::DataTypeManagerImpl(
48    const base::Closure& unrecoverable_error_method,
49    const syncer::WeakHandle<syncer::DataTypeDebugInfoListener>&
50        debug_info_listener,
51    const DataTypeController::TypeMap* controllers,
52    const DataTypeEncryptionHandler* encryption_handler,
53    BackendDataTypeConfigurer* configurer,
54    DataTypeManagerObserver* observer)
55    : configurer_(configurer),
56      controllers_(controllers),
57      state_(DataTypeManager::STOPPED),
58      needs_reconfigure_(false),
59      last_configure_reason_(syncer::CONFIGURE_REASON_UNKNOWN),
60      debug_info_listener_(debug_info_listener),
61      model_association_manager_(controllers, this),
62      observer_(observer),
63      encryption_handler_(encryption_handler),
64      unrecoverable_error_method_(unrecoverable_error_method),
65      weak_ptr_factory_(this) {
66  DCHECK(configurer_);
67  DCHECK(observer_);
68}
69
70DataTypeManagerImpl::~DataTypeManagerImpl() {}
71
72void DataTypeManagerImpl::Configure(syncer::ModelTypeSet desired_types,
73                                    syncer::ConfigureReason reason) {
74  if (reason == syncer::CONFIGURE_REASON_BACKUP_ROLLBACK)
75    desired_types.PutAll(syncer::ControlTypes());
76  else
77    desired_types.PutAll(syncer::CoreTypes());
78
79  // Only allow control types and types that have controllers.
80  syncer::ModelTypeSet filtered_desired_types;
81  for (syncer::ModelTypeSet::Iterator type = desired_types.First();
82      type.Good(); type.Inc()) {
83    DataTypeController::TypeMap::const_iterator iter =
84        controllers_->find(type.Get());
85    if (syncer::IsControlType(type.Get()) ||
86        iter != controllers_->end()) {
87      if (iter != controllers_->end()) {
88        if (!iter->second->ReadyForStart() &&
89            !data_type_status_table_.GetUnreadyErrorTypes().Has(
90                type.Get())) {
91          // Add the type to the unready types set to prevent purging it. It's
92          // up to the datatype controller to, if necessary, explicitly
93          // mark the type as broken to trigger a purge.
94          syncer::SyncError error(FROM_HERE,
95                                  syncer::SyncError::UNREADY_ERROR,
96                                  "Datatype not ready at config time.",
97                                  type.Get());
98          std::map<syncer::ModelType, syncer::SyncError> errors;
99          errors[type.Get()] = error;
100          data_type_status_table_.UpdateFailedDataTypes(errors);
101        } else if (iter->second->ReadyForStart()) {
102          data_type_status_table_.ResetUnreadyErrorFor(type.Get());
103        }
104      }
105      filtered_desired_types.Put(type.Get());
106    }
107  }
108  ConfigureImpl(filtered_desired_types, reason);
109}
110
111void DataTypeManagerImpl::ReenableType(syncer::ModelType type) {
112  // TODO(zea): move the "should we reconfigure" logic into the datatype handler
113  // itself.
114  // Only reconfigure if the type actually had a data type or unready error.
115  if (!data_type_status_table_.ResetDataTypeErrorFor(type) &&
116      !data_type_status_table_.ResetUnreadyErrorFor(type)) {
117    return;
118  }
119
120  // Only reconfigure if the type is actually desired.
121  if (!last_requested_types_.Has(type))
122    return;
123
124  DVLOG(1) << "Reenabling " << syncer::ModelTypeToString(type);
125  needs_reconfigure_ = true;
126  last_configure_reason_ = syncer::CONFIGURE_REASON_PROGRAMMATIC;
127  ProcessReconfigure();
128}
129
130void DataTypeManagerImpl::ResetDataTypeErrors() {
131  data_type_status_table_.Reset();
132}
133
134void DataTypeManagerImpl::PurgeForMigration(
135    syncer::ModelTypeSet undesired_types,
136    syncer::ConfigureReason reason) {
137  syncer::ModelTypeSet remainder = Difference(last_requested_types_,
138                                              undesired_types);
139  ConfigureImpl(remainder, reason);
140}
141
142void DataTypeManagerImpl::ConfigureImpl(
143    syncer::ModelTypeSet desired_types,
144    syncer::ConfigureReason reason) {
145  DCHECK_NE(reason, syncer::CONFIGURE_REASON_UNKNOWN);
146  DVLOG(1) << "Configuring for " << syncer::ModelTypeSetToString(desired_types)
147           << " with reason " << reason;
148  if (state_ == STOPPING) {
149    // You can not set a configuration while stopping.
150    LOG(ERROR) << "Configuration set while stopping.";
151    return;
152  }
153
154  // TODO(zea): consider not performing a full configuration once there's a
155  // reliable way to determine if the requested set of enabled types matches the
156  // current set.
157
158  last_requested_types_ = desired_types;
159  last_configure_reason_ = reason;
160  // Only proceed if we're in a steady state or retrying.
161  if (state_ != STOPPED && state_ != CONFIGURED && state_ != RETRYING) {
162    DVLOG(1) << "Received configure request while configuration in flight. "
163             << "Postponing until current configuration complete.";
164    needs_reconfigure_ = true;
165    return;
166  }
167
168  Restart(reason);
169}
170
171BackendDataTypeConfigurer::DataTypeConfigStateMap
172DataTypeManagerImpl::BuildDataTypeConfigStateMap(
173    const syncer::ModelTypeSet& types_being_configured) const {
174  // 1. Get the failed types (due to fatal, crypto, and unready errors).
175  // 2. Add the difference between last_requested_types_ and the failed types
176  //    as CONFIGURE_INACTIVE.
177  // 3. Flip |types_being_configured| to CONFIGURE_ACTIVE.
178  // 4. Set non-enabled user types as DISABLED.
179  // 5. Set the fatal, crypto, and unready types to their respective states.
180  syncer::ModelTypeSet error_types =
181      data_type_status_table_.GetFailedTypes();
182  syncer::ModelTypeSet fatal_types =
183      data_type_status_table_.GetFatalErrorTypes();
184  syncer::ModelTypeSet crypto_types =
185      data_type_status_table_.GetCryptoErrorTypes();
186  syncer::ModelTypeSet unready_types=
187      data_type_status_table_.GetUnreadyErrorTypes();
188
189  // Types with persistence errors are only purged/resynced when they're
190  // actively being configured.
191  syncer::ModelTypeSet persistence_types =
192      data_type_status_table_.GetPersistenceErrorTypes();
193  persistence_types.RetainAll(types_being_configured);
194
195  // Types with unready errors do not count as unready if they've been disabled.
196  unready_types.RetainAll(last_requested_types_);
197
198  syncer::ModelTypeSet enabled_types = last_requested_types_;
199  enabled_types.RemoveAll(error_types);
200  syncer::ModelTypeSet disabled_types =
201      syncer::Difference(
202          syncer::Union(syncer::UserTypes(), syncer::ControlTypes()),
203          enabled_types);
204  syncer::ModelTypeSet to_configure = syncer::Intersection(
205      enabled_types, types_being_configured);
206  DVLOG(1) << "Enabling: " << syncer::ModelTypeSetToString(enabled_types);
207  DVLOG(1) << "Configuring: " << syncer::ModelTypeSetToString(to_configure);
208  DVLOG(1) << "Disabling: " << syncer::ModelTypeSetToString(disabled_types);
209
210  BackendDataTypeConfigurer::DataTypeConfigStateMap config_state_map;
211  BackendDataTypeConfigurer::SetDataTypesState(
212      BackendDataTypeConfigurer::CONFIGURE_INACTIVE, enabled_types,
213      &config_state_map);
214  BackendDataTypeConfigurer::SetDataTypesState(
215      BackendDataTypeConfigurer::CONFIGURE_ACTIVE, to_configure,
216      &config_state_map);
217  BackendDataTypeConfigurer::SetDataTypesState(
218      BackendDataTypeConfigurer::CONFIGURE_CLEAN, persistence_types,
219        &config_state_map);
220  BackendDataTypeConfigurer::SetDataTypesState(
221      BackendDataTypeConfigurer::DISABLED, disabled_types,
222      &config_state_map);
223  BackendDataTypeConfigurer::SetDataTypesState(
224      BackendDataTypeConfigurer::FATAL, fatal_types,
225      &config_state_map);
226  BackendDataTypeConfigurer::SetDataTypesState(
227      BackendDataTypeConfigurer::CRYPTO, crypto_types,
228        &config_state_map);
229  BackendDataTypeConfigurer::SetDataTypesState(
230      BackendDataTypeConfigurer::UNREADY, unready_types,
231        &config_state_map);
232  return config_state_map;
233}
234
235void DataTypeManagerImpl::Restart(syncer::ConfigureReason reason) {
236  DVLOG(1) << "Restarting...";
237
238  // Check for new or resolved data type crypto errors.
239  if (encryption_handler_->IsPassphraseRequired()) {
240    syncer::ModelTypeSet encrypted_types =
241        encryption_handler_->GetEncryptedDataTypes();
242    encrypted_types.RetainAll(last_requested_types_);
243    encrypted_types.RemoveAll(
244        data_type_status_table_.GetCryptoErrorTypes());
245    DataTypeStatusTable::TypeErrorMap crypto_errors =
246        GenerateCryptoErrorsForTypes(encrypted_types);
247    data_type_status_table_.UpdateFailedDataTypes(crypto_errors);
248  } else {
249    data_type_status_table_.ResetCryptoErrors();
250  }
251
252  syncer::ModelTypeSet failed_types =
253      data_type_status_table_.GetFailedTypes();
254  syncer::ModelTypeSet enabled_types =
255      syncer::Difference(last_requested_types_, failed_types);
256
257  last_restart_time_ = base::Time::Now();
258  configuration_stats_.clear();
259
260  DCHECK(state_ == STOPPED || state_ == CONFIGURED || state_ == RETRYING);
261
262  // Starting from a "steady state" (stopped or configured) state
263  // should send a start notification.
264  if (state_ == STOPPED || state_ == CONFIGURED)
265    NotifyStart();
266
267  state_ = DOWNLOAD_PENDING;
268  download_types_queue_ = PrioritizeTypes(enabled_types);
269  association_types_queue_ = std::queue<AssociationTypesInfo>();
270
271  model_association_manager_.Initialize(enabled_types);
272
273  // Tell the backend about the new set of data types we wish to sync.
274  // The task will be invoked when updates are downloaded.
275  configurer_->ConfigureDataTypes(
276      reason,
277      BuildDataTypeConfigStateMap(download_types_queue_.front()),
278      base::Bind(&DataTypeManagerImpl::DownloadReady,
279                 weak_ptr_factory_.GetWeakPtr(),
280                 base::Time::Now(),
281                 download_types_queue_.front(),
282                 syncer::ModelTypeSet()),
283      base::Bind(&DataTypeManagerImpl::OnDownloadRetry,
284                 weak_ptr_factory_.GetWeakPtr()));
285}
286
287syncer::ModelTypeSet DataTypeManagerImpl::GetPriorityTypes() const {
288  syncer::ModelTypeSet high_priority_types;
289  high_priority_types.PutAll(syncer::PriorityCoreTypes());
290  high_priority_types.PutAll(syncer::PriorityUserTypes());
291  return high_priority_types;
292}
293
294TypeSetPriorityList DataTypeManagerImpl::PrioritizeTypes(
295    const syncer::ModelTypeSet& types) {
296  syncer::ModelTypeSet high_priority_types = GetPriorityTypes();
297  high_priority_types.RetainAll(types);
298
299  syncer::ModelTypeSet low_priority_types =
300      syncer::Difference(types, high_priority_types);
301
302  TypeSetPriorityList result;
303  if (!high_priority_types.Empty())
304    result.push(high_priority_types);
305  if (!low_priority_types.Empty())
306    result.push(low_priority_types);
307
308  // Could be empty in case of purging for migration, sync nothing, etc.
309  // Configure empty set to purge data from backend.
310  if (result.empty())
311    result.push(syncer::ModelTypeSet());
312
313  return result;
314}
315
316void DataTypeManagerImpl::ProcessReconfigure() {
317  DCHECK(needs_reconfigure_);
318
319  // Wait for current download and association to finish.
320  if (!(download_types_queue_.empty() && association_types_queue_.empty()))
321    return;
322
323  // An attempt was made to reconfigure while we were already configuring.
324  // This can be because a passphrase was accepted or the user changed the
325  // set of desired types. Either way, |last_requested_types_| will contain
326  // the most recent set of desired types, so we just call configure.
327  // Note: we do this whether or not GetControllersNeedingStart is true,
328  // because we may need to stop datatypes.
329  DVLOG(1) << "Reconfiguring due to previous configure attempt occuring while"
330           << " busy.";
331
332  // Note: ConfigureImpl is called directly, rather than posted, in order to
333  // ensure that any purging/unapplying/journaling happens while the set of
334  // failed types is still up to date. If stack unwinding were to be done
335  // via PostTask, the failed data types may be reset before the purging was
336  // performed.
337  state_ = RETRYING;
338  needs_reconfigure_ = false;
339  ConfigureImpl(last_requested_types_, last_configure_reason_);
340}
341
342void DataTypeManagerImpl::OnDownloadRetry() {
343  DCHECK(state_ == DOWNLOAD_PENDING || state_ == CONFIGURING);
344  observer_->OnConfigureRetry();
345}
346
347void DataTypeManagerImpl::DownloadReady(
348    base::Time download_start_time,
349    syncer::ModelTypeSet types_to_download,
350    syncer::ModelTypeSet high_priority_types_before,
351    syncer::ModelTypeSet first_sync_types,
352    syncer::ModelTypeSet failed_configuration_types) {
353  DCHECK(state_ == DOWNLOAD_PENDING || state_ == CONFIGURING);
354
355  // Persistence errors are reset after each backend configuration attempt
356  // during which they would have been purged.
357  data_type_status_table_.ResetPersistenceErrorsFrom(types_to_download);
358
359  // Ignore |failed_configuration_types| if we need to reconfigure
360  // anyway.
361  if (needs_reconfigure_) {
362    download_types_queue_ = TypeSetPriorityList();
363    ProcessReconfigure();
364    return;
365  }
366
367  if (!failed_configuration_types.Empty()) {
368    if (!unrecoverable_error_method_.is_null())
369      unrecoverable_error_method_.Run();
370    DataTypeStatusTable::TypeErrorMap errors;
371    for (syncer::ModelTypeSet::Iterator iter =
372             failed_configuration_types.First(); iter.Good(); iter.Inc()) {
373      syncer::SyncError error(
374          FROM_HERE,
375          syncer::SyncError::UNRECOVERABLE_ERROR,
376          "Backend failed to download type.",
377          iter.Get());
378      errors[iter.Get()] = error;
379    }
380    data_type_status_table_.UpdateFailedDataTypes(errors);
381    Abort(UNRECOVERABLE_ERROR);
382    return;
383  }
384
385  state_ = CONFIGURING;
386
387  // Pop and associate download-ready types.
388  syncer::ModelTypeSet ready_types = types_to_download;
389  CHECK(!download_types_queue_.empty());
390  download_types_queue_.pop();
391  syncer::ModelTypeSet new_types_to_download;
392  if (!download_types_queue_.empty())
393    new_types_to_download = download_types_queue_.front();
394
395  AssociationTypesInfo association_info;
396  association_info.types = ready_types;
397  association_info.first_sync_types = first_sync_types;
398  association_info.download_start_time = download_start_time;
399  association_info.download_ready_time = base::Time::Now();
400  association_info.high_priority_types_before = high_priority_types_before;
401  association_types_queue_.push(association_info);
402  if (association_types_queue_.size() == 1u)
403    StartNextAssociation();
404
405  // Download types of low priority while configuring types of high priority.
406  if (!new_types_to_download.Empty()) {
407    configurer_->ConfigureDataTypes(
408        last_configure_reason_,
409        BuildDataTypeConfigStateMap(new_types_to_download),
410        base::Bind(&DataTypeManagerImpl::DownloadReady,
411                   weak_ptr_factory_.GetWeakPtr(),
412                   base::Time::Now(),
413                   new_types_to_download,
414                   syncer::Union(ready_types, high_priority_types_before)),
415        base::Bind(&DataTypeManagerImpl::OnDownloadRetry,
416                   weak_ptr_factory_.GetWeakPtr()));
417  }
418}
419
420void DataTypeManagerImpl::StartNextAssociation() {
421  CHECK(!association_types_queue_.empty());
422
423  association_types_queue_.front().association_request_time =
424      base::Time::Now();
425  model_association_manager_.StartAssociationAsync(
426      association_types_queue_.front().types);
427}
428
429void DataTypeManagerImpl::OnSingleDataTypeWillStop(
430    syncer::ModelType type,
431    const syncer::SyncError& error) {
432  configurer_->DeactivateDataType(type);
433  if (error.IsSet()) {
434    DataTypeStatusTable::TypeErrorMap failed_types;
435    failed_types[type] = error;
436    data_type_status_table_.UpdateFailedDataTypes(
437            failed_types);
438
439    // Unrecoverable errors will shut down the entire backend, so no need to
440    // reconfigure.
441    if (error.error_type() != syncer::SyncError::UNRECOVERABLE_ERROR) {
442      needs_reconfigure_ = true;
443      last_configure_reason_ = syncer::CONFIGURE_REASON_PROGRAMMATIC;
444      ProcessReconfigure();
445    }
446  }
447}
448
449void DataTypeManagerImpl::OnSingleDataTypeAssociationDone(
450    syncer::ModelType type,
451    const syncer::DataTypeAssociationStats& association_stats) {
452  DCHECK(!association_types_queue_.empty());
453  DataTypeController::TypeMap::const_iterator c_it = controllers_->find(type);
454  DCHECK(c_it != controllers_->end());
455  if (c_it->second->state() == DataTypeController::RUNNING) {
456    // Tell the backend about the change processor for this type so it can
457    // begin routing changes to it.
458    configurer_->ActivateDataType(type, c_it->second->model_safe_group(),
459                                  c_it->second->GetChangeProcessor());
460  }
461
462  if (!debug_info_listener_.IsInitialized())
463    return;
464
465  AssociationTypesInfo& info = association_types_queue_.front();
466  configuration_stats_.push_back(syncer::DataTypeConfigurationStats());
467  configuration_stats_.back().model_type = type;
468  configuration_stats_.back().association_stats = association_stats;
469  if (info.types.Has(type)) {
470    // Times in |info| only apply to non-slow types.
471    configuration_stats_.back().download_wait_time =
472        info.download_start_time - last_restart_time_;
473    if (info.first_sync_types.Has(type)) {
474      configuration_stats_.back().download_time =
475          info.download_ready_time - info.download_start_time;
476    }
477    configuration_stats_.back().association_wait_time_for_high_priority =
478        info.association_request_time - info.download_ready_time;
479    configuration_stats_.back().high_priority_types_configured_before =
480        info.high_priority_types_before;
481    configuration_stats_.back().same_priority_types_configured_before =
482        info.configured_types;
483    info.configured_types.Put(type);
484  }
485}
486
487void DataTypeManagerImpl::OnModelAssociationDone(
488    const DataTypeManager::ConfigureResult& result) {
489  DCHECK(state_ == STOPPING || state_ == CONFIGURING);
490
491  if (state_ == STOPPING)
492    return;
493
494  // Ignore abort/unrecoverable error if we need to reconfigure anyways.
495  if (needs_reconfigure_) {
496    association_types_queue_ = std::queue<AssociationTypesInfo>();
497    ProcessReconfigure();
498    return;
499  }
500
501  if (result.status == ABORTED || result.status == UNRECOVERABLE_ERROR) {
502    Abort(result.status);
503    return;
504  }
505
506  DCHECK(result.status == OK);
507
508  CHECK(!association_types_queue_.empty());
509  association_types_queue_.pop();
510  if (!association_types_queue_.empty()) {
511    StartNextAssociation();
512  } else if (download_types_queue_.empty()) {
513    state_ = CONFIGURED;
514    NotifyDone(result);
515  }
516}
517
518void DataTypeManagerImpl::Stop() {
519  if (state_ == STOPPED)
520    return;
521
522  bool need_to_notify =
523      state_ == DOWNLOAD_PENDING || state_ == CONFIGURING;
524  StopImpl();
525
526  if (need_to_notify) {
527    ConfigureResult result(ABORTED,
528                           last_requested_types_);
529    NotifyDone(result);
530  }
531}
532
533void DataTypeManagerImpl::Abort(ConfigureStatus status) {
534  DCHECK(state_ == DOWNLOAD_PENDING || state_ == CONFIGURING);
535
536  StopImpl();
537
538  DCHECK_NE(OK, status);
539  ConfigureResult result(status,
540                         last_requested_types_);
541  NotifyDone(result);
542}
543
544void DataTypeManagerImpl::StopImpl() {
545  state_ = STOPPING;
546
547  // Invalidate weak pointer to drop download callbacks.
548  weak_ptr_factory_.InvalidateWeakPtrs();
549
550  // Stop all data types. This may trigger association callback but the
551  // callback will do nothing because state is set to STOPPING above.
552  model_association_manager_.Stop();
553
554  state_ = STOPPED;
555}
556
557void DataTypeManagerImpl::NotifyStart() {
558  observer_->OnConfigureStart();
559}
560
561void DataTypeManagerImpl::NotifyDone(const ConfigureResult& raw_result) {
562  AddToConfigureTime();
563
564  ConfigureResult result = raw_result;
565  result.data_type_status_table = data_type_status_table_;
566
567  DVLOG(1) << "Total time spent configuring: "
568           << configure_time_delta_.InSecondsF() << "s";
569  switch (result.status) {
570    case DataTypeManager::OK:
571      DVLOG(1) << "NotifyDone called with result: OK";
572      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.OK",
573                               configure_time_delta_);
574      if (debug_info_listener_.IsInitialized() &&
575          !configuration_stats_.empty()) {
576        debug_info_listener_.Call(
577            FROM_HERE,
578            &syncer::DataTypeDebugInfoListener::OnDataTypeConfigureComplete,
579            configuration_stats_);
580      }
581      configuration_stats_.clear();
582      break;
583    case DataTypeManager::ABORTED:
584      DVLOG(1) << "NotifyDone called with result: ABORTED";
585      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.ABORTED",
586                               configure_time_delta_);
587      break;
588    case DataTypeManager::UNRECOVERABLE_ERROR:
589      DVLOG(1) << "NotifyDone called with result: UNRECOVERABLE_ERROR";
590      UMA_HISTOGRAM_LONG_TIMES("Sync.ConfigureTime_Long.UNRECOVERABLE_ERROR",
591                               configure_time_delta_);
592      break;
593    case DataTypeManager::UNKNOWN:
594      NOTREACHED();
595      break;
596  }
597  observer_->OnConfigureDone(result);
598}
599
600DataTypeManager::State DataTypeManagerImpl::state() const {
601  return state_;
602}
603
604void DataTypeManagerImpl::AddToConfigureTime() {
605  DCHECK(!last_restart_time_.is_null());
606  configure_time_delta_ += (base::Time::Now() - last_restart_time_);
607}
608
609}  // namespace sync_driver
610