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/model_association_manager.h"
6
7 #include <algorithm>
8 #include <functional>
9
10 #include "base/debug/trace_event.h"
11 #include "base/logging.h"
12 #include "base/message_loop/message_loop.h"
13 #include "base/metrics/histogram.h"
14 #include "sync/internal_api/public/base/model_type.h"
15
16 using syncer::ModelTypeSet;
17
18 namespace sync_driver {
19
20 namespace {
21
22 static const syncer::ModelType kStartOrder[] = {
23 syncer::NIGORI, // Listed for completeness.
24 syncer::DEVICE_INFO, // Listed for completeness.
25 syncer::EXPERIMENTS, // Listed for completeness.
26 syncer::PROXY_TABS, // Listed for completeness.
27
28 // Kick off the association of the non-UI types first so they can associate
29 // in parallel with the UI types.
30 syncer::PASSWORDS,
31 syncer::AUTOFILL,
32 syncer::AUTOFILL_PROFILE,
33 syncer::EXTENSION_SETTINGS,
34 syncer::APP_SETTINGS,
35 syncer::TYPED_URLS,
36 syncer::HISTORY_DELETE_DIRECTIVES,
37 syncer::SYNCED_NOTIFICATIONS,
38 syncer::SYNCED_NOTIFICATION_APP_INFO,
39
40 // UI thread data types.
41 syncer::BOOKMARKS,
42 syncer::SUPERVISED_USERS, // Syncing supervised users on initial login
43 // might block creating a new supervised user,
44 // so we want to do it early.
45 syncer::PREFERENCES,
46 syncer::PRIORITY_PREFERENCES,
47 syncer::EXTENSIONS,
48 syncer::APPS,
49 syncer::APP_LIST,
50 syncer::THEMES,
51 syncer::SEARCH_ENGINES,
52 syncer::SESSIONS,
53 syncer::APP_NOTIFICATIONS,
54 syncer::DICTIONARY,
55 syncer::FAVICON_IMAGES,
56 syncer::FAVICON_TRACKING,
57 syncer::SUPERVISED_USER_SETTINGS,
58 syncer::SUPERVISED_USER_SHARED_SETTINGS,
59 syncer::ARTICLES,
60 };
61
62 COMPILE_ASSERT(arraysize(kStartOrder) ==
63 syncer::MODEL_TYPE_COUNT - syncer::FIRST_REAL_MODEL_TYPE,
64 kStartOrder_IncorrectSize);
65
66 // The amount of time we wait for association to finish. If some types haven't
67 // finished association by the time, DataTypeManager is notified of the
68 // unfinished types.
69 const int64 kAssociationTimeOutInSeconds = 600;
70
BuildAssociationStatsFromMergeResults(const syncer::SyncMergeResult & local_merge_result,const syncer::SyncMergeResult & syncer_merge_result,const base::TimeDelta & association_wait_time,const base::TimeDelta & association_time)71 syncer::DataTypeAssociationStats BuildAssociationStatsFromMergeResults(
72 const syncer::SyncMergeResult& local_merge_result,
73 const syncer::SyncMergeResult& syncer_merge_result,
74 const base::TimeDelta& association_wait_time,
75 const base::TimeDelta& association_time) {
76 DCHECK_EQ(local_merge_result.model_type(), syncer_merge_result.model_type());
77 syncer::DataTypeAssociationStats stats;
78 stats.had_error = local_merge_result.error().IsSet() ||
79 syncer_merge_result.error().IsSet();
80 stats.num_local_items_before_association =
81 local_merge_result.num_items_before_association();
82 stats.num_sync_items_before_association =
83 syncer_merge_result.num_items_before_association();
84 stats.num_local_items_after_association =
85 local_merge_result.num_items_after_association();
86 stats.num_sync_items_after_association =
87 syncer_merge_result.num_items_after_association();
88 stats.num_local_items_added =
89 local_merge_result.num_items_added();
90 stats.num_local_items_deleted =
91 local_merge_result.num_items_deleted();
92 stats.num_local_items_modified =
93 local_merge_result.num_items_modified();
94 stats.local_version_pre_association =
95 local_merge_result.pre_association_version();
96 stats.num_sync_items_added =
97 syncer_merge_result.num_items_added();
98 stats.num_sync_items_deleted =
99 syncer_merge_result.num_items_deleted();
100 stats.num_sync_items_modified =
101 syncer_merge_result.num_items_modified();
102 stats.sync_version_pre_association =
103 syncer_merge_result.pre_association_version();
104 stats.association_wait_time = association_wait_time;
105 stats.association_time = association_time;
106 return stats;
107 }
108
109 } // namespace
110
ModelAssociationManager(const DataTypeController::TypeMap * controllers,ModelAssociationManagerDelegate * processor)111 ModelAssociationManager::ModelAssociationManager(
112 const DataTypeController::TypeMap* controllers,
113 ModelAssociationManagerDelegate* processor)
114 : state_(IDLE),
115 controllers_(controllers),
116 delegate_(processor),
117 configure_status_(DataTypeManager::UNKNOWN),
118 weak_ptr_factory_(this) {
119 // Ensure all data type controllers are stopped.
120 for (DataTypeController::TypeMap::const_iterator it = controllers_->begin();
121 it != controllers_->end(); ++it) {
122 DCHECK_EQ(DataTypeController::NOT_RUNNING, (*it).second->state());
123 }
124 }
125
~ModelAssociationManager()126 ModelAssociationManager::~ModelAssociationManager() {
127 }
128
Initialize(syncer::ModelTypeSet desired_types)129 void ModelAssociationManager::Initialize(syncer::ModelTypeSet desired_types) {
130 // state_ can be INITIALIZED_TO_CONFIGURE if types are reconfigured when
131 // data is being downloaded, so StartAssociationAsync() is never called for
132 // the first configuration.
133 DCHECK_NE(CONFIGURING, state_);
134
135 // Only keep types that have controllers.
136 desired_types_.Clear();
137 for (syncer::ModelTypeSet::Iterator it = desired_types.First();
138 it.Good(); it.Inc()) {
139 if (controllers_->find(it.Get()) != controllers_->end())
140 desired_types_.Put(it.Get());
141 }
142
143 DVLOG(1) << "ModelAssociationManager: Initializing for "
144 << syncer::ModelTypeSetToString(desired_types_);
145
146 state_ = INITIALIZED_TO_CONFIGURE;
147
148 StopDisabledTypes();
149 LoadEnabledTypes();
150 }
151
StopDatatype(const syncer::SyncError & error,DataTypeController * dtc)152 void ModelAssociationManager::StopDatatype(
153 const syncer::SyncError& error,
154 DataTypeController* dtc) {
155 loaded_types_.Remove(dtc->type());
156 associated_types_.Remove(dtc->type());
157 associating_types_.Remove(dtc->type());
158
159 if (error.IsSet() || dtc->state() != DataTypeController::NOT_RUNNING) {
160 // If an error was set, the delegate must be informed of the error.
161 delegate_->OnSingleDataTypeWillStop(dtc->type(), error);
162 dtc->Stop();
163 }
164 }
165
StopDisabledTypes()166 void ModelAssociationManager::StopDisabledTypes() {
167 DVLOG(1) << "ModelAssociationManager: Stopping disabled types.";
168 for (DataTypeController::TypeMap::const_iterator it = controllers_->begin();
169 it != controllers_->end(); ++it) {
170 DataTypeController* dtc = (*it).second.get();
171 if (dtc->state() != DataTypeController::NOT_RUNNING &&
172 !desired_types_.Has(dtc->type())) {
173 DVLOG(1) << "ModelTypeToString: stop " << dtc->name();
174 StopDatatype(syncer::SyncError(), dtc);
175 }
176 }
177 }
178
LoadEnabledTypes()179 void ModelAssociationManager::LoadEnabledTypes() {
180 // Load in kStartOrder.
181 for (size_t i = 0; i < arraysize(kStartOrder); i++) {
182 syncer::ModelType type = kStartOrder[i];
183 if (!desired_types_.Has(type))
184 continue;
185
186 DCHECK(controllers_->find(type) != controllers_->end());
187 DataTypeController* dtc = controllers_->find(type)->second.get();
188 if (dtc->state() == DataTypeController::NOT_RUNNING) {
189 DCHECK(!loaded_types_.Has(dtc->type()));
190 DCHECK(!associated_types_.Has(dtc->type()));
191 dtc->LoadModels(base::Bind(&ModelAssociationManager::ModelLoadCallback,
192 weak_ptr_factory_.GetWeakPtr()));
193 }
194 }
195 }
196
StartAssociationAsync(const syncer::ModelTypeSet & types_to_associate)197 void ModelAssociationManager::StartAssociationAsync(
198 const syncer::ModelTypeSet& types_to_associate) {
199 DCHECK_NE(CONFIGURING, state_);
200 state_ = CONFIGURING;
201
202 association_start_time_ = base::TimeTicks::Now();
203
204 requested_types_ = types_to_associate;
205
206 associating_types_ = types_to_associate;
207 associating_types_.RetainAll(desired_types_);
208 associating_types_.RemoveAll(associated_types_);
209
210 // Assume success.
211 configure_status_ = DataTypeManager::OK;
212
213 // Done if no types to associate.
214 if (associating_types_.Empty()) {
215 ModelAssociationDone();
216 return;
217 }
218
219 timer_.Start(FROM_HERE,
220 base::TimeDelta::FromSeconds(kAssociationTimeOutInSeconds),
221 this,
222 &ModelAssociationManager::ModelAssociationDone);
223
224 // Start association of types that are loaded in specified order.
225 for (size_t i = 0; i < arraysize(kStartOrder); i++) {
226 syncer::ModelType type = kStartOrder[i];
227 if (!associating_types_.Has(type) || !loaded_types_.Has(type))
228 continue;
229
230 DataTypeController* dtc = controllers_->find(type)->second.get();
231 DCHECK(DataTypeController::MODEL_LOADED == dtc->state() ||
232 DataTypeController::ASSOCIATING == dtc->state());
233 if (dtc->state() == DataTypeController::MODEL_LOADED) {
234 TRACE_EVENT_ASYNC_BEGIN1("sync", "ModelAssociation",
235 dtc,
236 "DataType",
237 ModelTypeToString(type));
238
239 dtc->StartAssociating(
240 base::Bind(&ModelAssociationManager::TypeStartCallback,
241 weak_ptr_factory_.GetWeakPtr(),
242 type, base::TimeTicks::Now()));
243 }
244 }
245 }
246
ResetForNextAssociation()247 void ModelAssociationManager::ResetForNextAssociation() {
248 DVLOG(1) << "ModelAssociationManager: Reseting for next configuration";
249 // |loaded_types_| and |associated_types_| are not cleared. So
250 // reconfiguration won't restart types that are already started.
251 requested_types_.Clear();
252 associating_types_.Clear();
253 }
254
Stop()255 void ModelAssociationManager::Stop() {
256 // Ignore callbacks from controllers.
257 weak_ptr_factory_.InvalidateWeakPtrs();
258
259 desired_types_.Clear();
260 loaded_types_.Clear();
261 associated_types_.Clear();
262 associating_types_.Clear();
263
264 // Stop started data types.
265 for (DataTypeController::TypeMap::const_iterator it = controllers_->begin();
266 it != controllers_->end(); ++it) {
267 DataTypeController* dtc = (*it).second.get();
268 if (dtc->state() != DataTypeController::NOT_RUNNING) {
269 StopDatatype(syncer::SyncError(), dtc);
270 DVLOG(1) << "ModelAssociationManager: Stopped " << dtc->name();
271 }
272 }
273
274 if (state_ == CONFIGURING) {
275 if (configure_status_ == DataTypeManager::OK)
276 configure_status_ = DataTypeManager::ABORTED;
277 DVLOG(1) << "ModelAssociationManager: Calling OnModelAssociationDone";
278 ModelAssociationDone();
279 }
280
281 ResetForNextAssociation();
282
283 state_ = IDLE;
284 }
285
ModelLoadCallback(syncer::ModelType type,syncer::SyncError error)286 void ModelAssociationManager::ModelLoadCallback(syncer::ModelType type,
287 syncer::SyncError error) {
288 DVLOG(1) << "ModelAssociationManager: ModelLoadCallback for "
289 << syncer::ModelTypeToString(type);
290
291 if (error.IsSet()) {
292 syncer::SyncMergeResult local_merge_result(type);
293 local_merge_result.set_error(error);
294 TypeStartCallback(type,
295 base::TimeTicks::Now(),
296 DataTypeController::ASSOCIATION_FAILED,
297 local_merge_result,
298 syncer::SyncMergeResult(type));
299 return;
300 }
301
302 // This happens when slow loading type is disabled by new configuration.
303 if (!desired_types_.Has(type))
304 return;
305
306 DCHECK(!loaded_types_.Has(type));
307 loaded_types_.Put(type);
308 if (associating_types_.Has(type)) {
309 DataTypeController* dtc = controllers_->find(type)->second.get();
310 dtc->StartAssociating(
311 base::Bind(&ModelAssociationManager::TypeStartCallback,
312 weak_ptr_factory_.GetWeakPtr(),
313 type, base::TimeTicks::Now()));
314 }
315 }
316
TypeStartCallback(syncer::ModelType type,base::TimeTicks type_start_time,DataTypeController::ConfigureResult start_result,const syncer::SyncMergeResult & local_merge_result,const syncer::SyncMergeResult & syncer_merge_result)317 void ModelAssociationManager::TypeStartCallback(
318 syncer::ModelType type,
319 base::TimeTicks type_start_time,
320 DataTypeController::ConfigureResult start_result,
321 const syncer::SyncMergeResult& local_merge_result,
322 const syncer::SyncMergeResult& syncer_merge_result) {
323 if (desired_types_.Has(type) &&
324 !DataTypeController::IsSuccessfulResult(start_result)) {
325 DVLOG(1) << "ModelAssociationManager: Type encountered an error.";
326 desired_types_.Remove(type);
327 DataTypeController* dtc = controllers_->find(type)->second.get();
328 StopDatatype(local_merge_result.error(), dtc);
329
330 // Update configuration result.
331 if (start_result == DataTypeController::UNRECOVERABLE_ERROR)
332 configure_status_ = DataTypeManager::UNRECOVERABLE_ERROR;
333 }
334
335 // This happens when a slow associating type is disabled or if a type
336 // disables itself after initial configuration.
337 if (!desired_types_.Has(type)) {
338 // It's possible all types failed to associate, in which case association
339 // is complete.
340 if (state_ == CONFIGURING && associating_types_.Empty())
341 ModelAssociationDone();
342 return;
343 }
344
345 DCHECK(!associated_types_.Has(type));
346 DCHECK(DataTypeController::IsSuccessfulResult(start_result));
347 associated_types_.Put(type);
348
349 if (state_ != CONFIGURING)
350 return;
351
352 TRACE_EVENT_ASYNC_END1("sync", "ModelAssociation",
353 controllers_->find(type)->second.get(),
354 "DataType",
355 ModelTypeToString(type));
356
357 // Track the merge results if we succeeded or an association failure
358 // occurred.
359 if (syncer::ProtocolTypes().Has(type)) {
360 base::TimeDelta association_wait_time =
361 std::max(base::TimeDelta(), type_start_time - association_start_time_);
362 base::TimeDelta association_time =
363 base::TimeTicks::Now() - type_start_time;;
364 syncer::DataTypeAssociationStats stats =
365 BuildAssociationStatsFromMergeResults(local_merge_result,
366 syncer_merge_result,
367 association_wait_time,
368 association_time);
369 delegate_->OnSingleDataTypeAssociationDone(type, stats);
370 }
371
372 associating_types_.Remove(type);
373
374 if (associating_types_.Empty())
375 ModelAssociationDone();
376 }
377
ModelAssociationDone()378 void ModelAssociationManager::ModelAssociationDone() {
379 CHECK_EQ(CONFIGURING, state_);
380
381 timer_.Stop();
382
383 // Treat any unfinished types as having errors.
384 desired_types_.RemoveAll(associating_types_);
385 for (DataTypeController::TypeMap::const_iterator it = controllers_->begin();
386 it != controllers_->end(); ++it) {
387 DataTypeController* dtc = (*it).second.get();
388 if (associating_types_.Has(dtc->type()) &&
389 dtc->state() != DataTypeController::NOT_RUNNING) {
390 UMA_HISTOGRAM_ENUMERATION("Sync.ConfigureFailed",
391 ModelTypeToHistogramInt(dtc->type()),
392 syncer::MODEL_TYPE_COUNT);
393 StopDatatype(syncer::SyncError(FROM_HERE,
394 syncer::SyncError::DATATYPE_ERROR,
395 "Association timed out.",
396 dtc->type()),
397 dtc);
398 }
399 }
400
401 DataTypeManager::ConfigureResult result(configure_status_,
402 requested_types_);
403
404 // Reset state before notifying |delegate_| because that might
405 // trigger a new round of configuration.
406 ResetForNextAssociation();
407 state_ = IDLE;
408
409 delegate_->OnModelAssociationDone(result);
410 }
411
412 base::OneShotTimer<ModelAssociationManager>*
GetTimerForTesting()413 ModelAssociationManager::GetTimerForTesting() {
414 return &timer_;
415 }
416
417 } // namespace sync_driver
418