Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(28)

Side by Side Diff: chrome/browser/sync/engine/conflict_resolver.cc

Issue 8922015: [Sync] Don't commit items with predecessors/parents in conflict. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: More comments. Add racy test case Created 8 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/sync/engine/conflict_resolver.h" 5 #include "chrome/browser/sync/engine/conflict_resolver.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <list>
8 #include <map> 9 #include <map>
9 #include <set> 10 #include <set>
10 11
11 #include "base/location.h" 12 #include "base/location.h"
12 #include "base/metrics/histogram.h" 13 #include "base/metrics/histogram.h"
13 #include "chrome/browser/sync/engine/syncer.h" 14 #include "chrome/browser/sync/engine/syncer.h"
14 #include "chrome/browser/sync/engine/syncer_util.h" 15 #include "chrome/browser/sync/engine/syncer_util.h"
15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" 16 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h"
16 #include "chrome/browser/sync/protocol/service_constants.h" 17 #include "chrome/browser/sync/protocol/service_constants.h"
17 #include "chrome/browser/sync/sessions/status_controller.h" 18 #include "chrome/browser/sync/sessions/status_controller.h"
18 #include "chrome/browser/sync/syncable/directory_manager.h" 19 #include "chrome/browser/sync/syncable/directory_manager.h"
19 #include "chrome/browser/sync/syncable/syncable.h" 20 #include "chrome/browser/sync/syncable/syncable.h"
20 #include "chrome/browser/sync/util/cryptographer.h" 21 #include "chrome/browser/sync/util/cryptographer.h"
21 22
23 using std::list;
22 using std::map; 24 using std::map;
23 using std::set; 25 using std::set;
24 using syncable::BaseTransaction; 26 using syncable::BaseTransaction;
25 using syncable::Directory; 27 using syncable::Directory;
26 using syncable::Entry; 28 using syncable::Entry;
27 using syncable::GetModelTypeFromSpecifics; 29 using syncable::GetModelTypeFromSpecifics;
28 using syncable::Id; 30 using syncable::Id;
29 using syncable::IsRealDataType; 31 using syncable::IsRealDataType;
30 using syncable::MutableEntry; 32 using syncable::MutableEntry;
31 using syncable::ScopedDirLookup; 33 using syncable::ScopedDirLookup;
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after
103 return NO_SYNC_PROGRESS; 105 return NO_SYNC_PROGRESS;
104 } 106 }
105 107
106 // This logic determines "client wins" vs. "server wins" strategy picking. 108 // This logic determines "client wins" vs. "server wins" strategy picking.
107 // By the time we get to this point, we rely on the following to be true: 109 // By the time we get to this point, we rely on the following to be true:
108 // a) We can decrypt both the local and server data (else we'd be in 110 // a) We can decrypt both the local and server data (else we'd be in
109 // conflict encryption and not attempting to resolve). 111 // conflict encryption and not attempting to resolve).
110 // b) All unsynced changes have been re-encrypted with the default key ( 112 // b) All unsynced changes have been re-encrypted with the default key (
111 // occurs either in AttemptToUpdateEntry, SetPassphrase, or 113 // occurs either in AttemptToUpdateEntry, SetPassphrase, or
112 // RefreshEncryption). 114 // RefreshEncryption).
113 // c) Prev_server_specifics having a valid datatype means that we received 115 // c) Base_server_specifics having a valid datatype means that we received
114 // an undecryptable update that only changed specifics, and since then have 116 // an undecryptable update that only changed specifics, and since then have
115 // not received any further non-specifics-only or decryptable updates. 117 // not received any further non-specifics-only or decryptable updates.
116 // d) If the server_specifics match specifics, server_specifics are 118 // d) If the server_specifics match specifics, server_specifics are
117 // encrypted with the default key, and all other visible properties match, 119 // encrypted with the default key, and all other visible properties match,
118 // then we can safely ignore the local changes as redundant. 120 // then we can safely ignore the local changes as redundant.
119 // e) Otherwise if the base_server_specifics match the server_specifics, no 121 // e) Otherwise if the base_server_specifics match the server_specifics, no
120 // functional change must have been made server-side (else 122 // functional change must have been made server-side (else
121 // base_server_specifics would have been cleared), and we can therefore 123 // base_server_specifics would have been cleared), and we can therefore
122 // safely ignore the server changes as redundant. 124 // safely ignore the server changes as redundant.
123 // f) Otherwise, it's in general safer to ignore local changes, with the 125 // f) Otherwise, it's in general safer to ignore local changes, with the
124 // exception of deletion conflicts (choose to undelete) and conflicts 126 // exception of deletion conflicts (choose to undelete) and conflicts
125 // where the non_unique_name or parent don't match. 127 // where the non_unique_name or parent don't match.
126 // TODO(sync): We can't compare position here without performing the
127 // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it
128 // into account.
129 if (!entry.Get(syncable::SERVER_IS_DEL)) { 128 if (!entry.Get(syncable::SERVER_IS_DEL)) {
130 // TODO(nick): The current logic is arbitrary; instead, it ought to be made 129 // TODO(nick): The current logic is arbitrary; instead, it ought to be made
131 // consistent with the ModelAssociator behavior for a datatype. It would 130 // consistent with the ModelAssociator behavior for a datatype. It would
132 // be nice if we could route this back to ModelAssociator code to pick one 131 // be nice if we could route this back to ModelAssociator code to pick one
133 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) 132 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill)
134 // are easily mergeable. 133 // are easily mergeable.
135 // See http://crbug.com/77339. 134 // See http://crbug.com/77339.
136 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == 135 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) ==
137 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); 136 entry.Get(syncable::SERVER_NON_UNIQUE_NAME);
138 bool parent_matches = entry.Get(syncable::PARENT_ID) == 137 bool parent_matches = entry.Get(syncable::PARENT_ID) ==
139 entry.Get(syncable::SERVER_PARENT_ID); 138 entry.Get(syncable::SERVER_PARENT_ID);
140 bool entry_deleted = entry.Get(syncable::IS_DEL); 139 bool entry_deleted = entry.Get(syncable::IS_DEL);
140
141 // This positional check is meant to be necessary but not sufficient. As a
142 // result, it may be false even when the position hasn't changed, possibly
143 // resulting in unnecessary commits, but if it's true the position has
144 // definitely not changed. The check works by verifying that the prev id
145 // as calculated from the server position (which will ignore any
146 // unsynced/unapplied predecessors and be root for non-bookmark datatypes)
147 // matches the client prev id. Because we traverse chains of conflicting
148 // items in predecessor -> successor order, we don't need to also verify the
149 // successor matches (If it's in conflict, we'll verify it next. If it's
150 // not, then it should be taken into account already in the
151 // ComputePrevIdFromServerPosition calculation). This works even when there
152 // are chains of conflicting items.
153 //
154 // Example: Original sequence was abcde. Server changes to aCDbe, while
155 // client changes to aDCbe (C and D are in conflict). Locally, D's prev id
156 // is a, while C's prev id is D. On the other hand, the server prev id will
157 // ignore unsynced/unapplied items, so D's server prev id will also be a,
158 // just like C's. Because we traverse in client predecessor->successor
159 // order, we evaluate D first. Since prev id and server id match, we
160 // consider the position to have remained the same for D, and will unset
161 // it's UNSYNCED/UNAPPLIED bits. When we evaluate C though, we'll see that
162 // the prev id is D locally while the server's prev id is a. C will
163 // therefore count as a positional conflict (and the local data will be
164 // overwritten by the server data typically). The final result will be
165 // aCDbe (the same as the server's view). Even though both C and D were
166 // modified, only one counted as being in actual conflict and was resolved
167 // with local/server wins.
168 //
169 // In general, when there are chains of positional conflicts, only the first
170 // item in chain (based on the clients point of view) will have both it's
171 // server prev id and local prev id match. For all the rest the server prev
172 // id will be the predecessor of the first item in the chain, and therefore
173 // not match the local prev id.
174 //
175 // Similarly, chains of conflicts where the server and client info are the
176 // same are supported due to the predecessor->successor ordering. In this
177 // case, from the first item onward, we unset the UNSYNCED/UNAPPLIED bits as
178 // we decide that nothing changed. The subsequent item's server prev id will
179 // accurately match the local prev id because the predecessor is no longer
180 // UNSYNCED/UNAPPLIED.
181 // TODO(zea): simplify all this once we can directly compare server position
182 // to client position.
183 syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition(
184 entry.Get(syncable::SERVER_PARENT_ID));
185 bool position_matches = parent_matches &&
186 server_prev_id == entry.Get(syncable::PREV_ID);
187 DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id "
188 << " is " << server_prev_id << ", local prev id is "
189 << entry.Get(syncable::PREV_ID);
141 const sync_pb::EntitySpecifics& specifics = 190 const sync_pb::EntitySpecifics& specifics =
142 entry.Get(syncable::SPECIFICS); 191 entry.Get(syncable::SPECIFICS);
143 const sync_pb::EntitySpecifics& server_specifics = 192 const sync_pb::EntitySpecifics& server_specifics =
144 entry.Get(syncable::SERVER_SPECIFICS); 193 entry.Get(syncable::SERVER_SPECIFICS);
145 const sync_pb::EntitySpecifics& base_server_specifics = 194 const sync_pb::EntitySpecifics& base_server_specifics =
146 entry.Get(syncable::BASE_SERVER_SPECIFICS); 195 entry.Get(syncable::BASE_SERVER_SPECIFICS);
147 std::string decrypted_specifics, decrypted_server_specifics; 196 std::string decrypted_specifics, decrypted_server_specifics;
148 bool specifics_match = false; 197 bool specifics_match = false;
149 bool server_encrypted_with_default_key = false; 198 bool server_encrypted_with_default_key = false;
150 if (specifics.has_encrypted()) { 199 if (specifics.has_encrypted()) {
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after
211 // We deliberately leave the server's device information. This client will 260 // We deliberately leave the server's device information. This client will
212 // add it's own device information on restart. 261 // add it's own device information on restart.
213 entry.Put(syncable::SPECIFICS, specifics); 262 entry.Put(syncable::SPECIFICS, specifics);
214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; 263 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry;
215 status->increment_num_server_overwrites(); 264 status->increment_num_server_overwrites();
216 OverwriteServerChanges(trans, &entry); 265 OverwriteServerChanges(trans, &entry);
217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 266 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
218 NIGORI_MERGE, 267 NIGORI_MERGE,
219 CONFLICT_RESOLUTION_SIZE); 268 CONFLICT_RESOLUTION_SIZE);
220 } else if (!entry_deleted && name_matches && parent_matches && 269 } else if (!entry_deleted && name_matches && parent_matches &&
221 specifics_match) { 270 position_matches && specifics_match) {
222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " 271 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
223 << "changes for: " << entry; 272 << "changes for: " << entry;
224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the 273 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the
225 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset 274 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data 275 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from
227 // when the server update gets applied and the item is re-inserted into 276 // adjacent entries when the server update gets applied and the item is
228 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing 277 // re-inserted into the PREV_ID/NEXT_ID linked list.
229 // positional info).
230 OverwriteServerChanges(trans, &entry); 278 OverwriteServerChanges(trans, &entry);
231 IgnoreLocalChanges(&entry); 279 IgnoreLocalChanges(&entry);
232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 280 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
233 CHANGES_MATCH, 281 CHANGES_MATCH,
234 CONFLICT_RESOLUTION_SIZE); 282 CONFLICT_RESOLUTION_SIZE);
235 } else if (base_server_specifics_match) { 283 } else if (base_server_specifics_match) {
236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " 284 DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
237 << " changes for: " << entry; 285 << " changes for: " << entry;
238 status->increment_num_server_overwrites(); 286 status->increment_num_server_overwrites();
239 OverwriteServerChanges(trans, &entry); 287 OverwriteServerChanges(trans, &entry);
240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 288 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
241 IGNORE_ENCRYPTION, 289 IGNORE_ENCRYPTION,
242 CONFLICT_RESOLUTION_SIZE); 290 CONFLICT_RESOLUTION_SIZE);
243 // Now that we've resolved the conflict, clear the prev server
244 // specifics.
245 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
246 } else if (entry_deleted || !name_matches || !parent_matches) { 291 } else if (entry_deleted || !name_matches || !parent_matches) {
247 OverwriteServerChanges(trans, &entry); 292 OverwriteServerChanges(trans, &entry);
248 status->increment_num_server_overwrites(); 293 status->increment_num_server_overwrites();
249 DVLOG(1) << "Resolving simple conflict, overwriting server changes " 294 DVLOG(1) << "Resolving simple conflict, overwriting server changes "
250 << "for: " << entry; 295 << "for: " << entry;
251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 296 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
252 OVERWRITE_SERVER, 297 OVERWRITE_SERVER,
253 CONFLICT_RESOLUTION_SIZE); 298 CONFLICT_RESOLUTION_SIZE);
254 } else { 299 } else {
255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " 300 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
256 << entry; 301 << entry;
257 IgnoreLocalChanges(&entry); 302 IgnoreLocalChanges(&entry);
258 status->increment_num_local_overwrites(); 303 status->increment_num_local_overwrites();
259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 304 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
260 OVERWRITE_LOCAL, 305 OVERWRITE_LOCAL,
261 CONFLICT_RESOLUTION_SIZE); 306 CONFLICT_RESOLUTION_SIZE);
262 } 307 }
308 // Now that we've resolved the conflict, clear the prev server
309 // specifics.
310 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
263 return SYNC_PROGRESS; 311 return SYNC_PROGRESS;
264 } else { // SERVER_IS_DEL is true 312 } else { // SERVER_IS_DEL is true
265 // If a server deleted folder has local contents we should be in a set. 313 // If a server deleted folder has local contents we should be in a set.
266 if (entry.Get(syncable::IS_DIR)) { 314 if (entry.Get(syncable::IS_DIR)) {
267 Directory::ChildHandles children; 315 Directory::ChildHandles children;
268 trans->directory()->GetChildHandlesById(trans, 316 trans->directory()->GetChildHandlesById(trans,
269 entry.Get(syncable::ID), 317 entry.Get(syncable::ID),
270 &children); 318 &children);
271 if (0 != children.size()) { 319 if (0 != children.size()) {
272 DVLOG(1) << "Entry is a server deleted directory with local contents, " 320 DVLOG(1) << "Entry is a server deleted directory with local contents, "
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
311 } 359 }
312 360
313 bool ConflictResolver::ResolveSimpleConflicts( 361 bool ConflictResolver::ResolveSimpleConflicts(
314 syncable::WriteTransaction* trans, 362 syncable::WriteTransaction* trans,
315 const Cryptographer* cryptographer, 363 const Cryptographer* cryptographer,
316 const ConflictProgress& progress, 364 const ConflictProgress& progress,
317 sessions::StatusController* status) { 365 sessions::StatusController* status) {
318 bool forward_progress = false; 366 bool forward_progress = false;
319 // First iterate over simple conflict items (those that belong to no set). 367 // First iterate over simple conflict items (those that belong to no set).
320 set<Id>::const_iterator conflicting_item_it; 368 set<Id>::const_iterator conflicting_item_it;
369 set<Id> processed_items;
321 for (conflicting_item_it = progress.ConflictingItemsBegin(); 370 for (conflicting_item_it = progress.ConflictingItemsBegin();
322 conflicting_item_it != progress.ConflictingItemsEnd(); 371 conflicting_item_it != progress.ConflictingItemsEnd();
323 ++conflicting_item_it) { 372 ++conflicting_item_it) {
324 Id id = *conflicting_item_it; 373 Id id = *conflicting_item_it;
374 if (processed_items.count(id) > 0)
375 continue;
325 map<Id, ConflictSet*>::const_iterator item_set_it = 376 map<Id, ConflictSet*>::const_iterator item_set_it =
326 progress.IdToConflictSetFind(id); 377 progress.IdToConflictSetFind(id);
327 if (item_set_it == progress.IdToConflictSetEnd() || 378 if (item_set_it == progress.IdToConflictSetEnd() ||
328 0 == item_set_it->second) { 379 0 == item_set_it->second) {
329 // We have a simple conflict. 380 // We have a simple conflict. In order check if positions have changed,
330 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { 381 // we need to process conflicting predecessors before successors. Traverse
331 case NO_SYNC_PROGRESS: 382 // backwards through all continuous conflicting predecessors, building a
383 // stack of items to resolve in predecessor->successor order, then process
384 // each item individually.
385 list<Id> predecessors;
386 Id prev_id = id;
387 do {
388 predecessors.push_back(prev_id);
389 Entry entry(trans, syncable::GET_BY_ID, prev_id);
390 // Any entry in conflict must be valid.
391 CHECK(entry.good());
392 Id new_prev_id = entry.Get(syncable::PREV_ID);
393 if (new_prev_id == prev_id)
332 break; 394 break;
333 case SYNC_PROGRESS: 395 prev_id = new_prev_id;
334 forward_progress = true; 396 } while (processed_items.count(prev_id) == 0 &&
335 break; 397 progress.HasSimpleConflictItem(prev_id)); // Excludes root.
398 while (!predecessors.empty()) {
399 id = predecessors.back();
400 predecessors.pop_back();
401 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) {
402 case NO_SYNC_PROGRESS:
403 break;
404 case SYNC_PROGRESS:
405 forward_progress = true;
406 break;
407 }
408 processed_items.insert(id);
336 } 409 }
337 } 410 }
338 } 411 }
339 return forward_progress; 412 return forward_progress;
340 } 413 }
341 414
342 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, 415 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans,
343 const Cryptographer* cryptographer, 416 const Cryptographer* cryptographer,
344 const ConflictProgress& progress, 417 const ConflictProgress& progress,
345 sessions::StatusController* status) { 418 sessions::StatusController* status) {
346 // TODO(rlarocque): A good amount of code related to the resolution of 419 // TODO(rlarocque): A good amount of code related to the resolution of
347 // conflict sets has been deleted here. This was done because the code had 420 // conflict sets has been deleted here. This was done because the code had
348 // not been used in years. An unrelated bug fix accidentally re-enabled the 421 // not been used in years. An unrelated bug fix accidentally re-enabled the
349 // code, forcing us to make a decision about what we should do with the code. 422 // code, forcing us to make a decision about what we should do with the code.
350 // We decided to do the safe thing and delete it for now. This restores the 423 // We decided to do the safe thing and delete it for now. This restores the
351 // behaviour we've relied on for quite some time. We should think about what 424 // behaviour we've relied on for quite some time. We should think about what
352 // that code was trying to do and consider re-enabling parts of it. 425 // that code was trying to do and consider re-enabling parts of it.
353 426
354 if (progress.ConflictSetsSize() > 0) { 427 if (progress.ConflictSetsSize() > 0) {
355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() 428 DVLOG(1) << "Detected " << progress.IdToConflictSetSize()
356 << " non-simple conflicting entries in " << progress.ConflictSetsSize() 429 << " non-simple conflicting entries in " << progress.ConflictSetsSize()
357 << " unprocessed conflict sets."; 430 << " unprocessed conflict sets.";
358 } 431 }
359 432
360 return ResolveSimpleConflicts(trans, cryptographer, progress, status); 433 return ResolveSimpleConflicts(trans, cryptographer, progress, status);
361 } 434 }
362 435
363 } // namespace browser_sync 436 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698