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

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: Ensure we clear BASE_SERVER_SPECIFICS 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 // This positional check is conservative. It will likely be false if the
141 // predecessor is unsynced/unapplied. For this reason, we ensure conflicting
142 // predecessors are resolved before their successors. Being
143 // conservative results in being less likely to lose local positioning
144 // changes, with the cost of occasional unnecessary commits.
145 // TODO(zea): change this once we can directly compare server position to
146 // client position.
147 syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition(
rlarocque 2012/01/10 23:39:51 ComputePrevIdFromServerPosition() will not return
Nicolas Zea 2012/01/18 02:54:39 See my updated comments. Basically, this situation
rlarocque 2012/01/18 20:21:55 It sounds like you're expecting this to return the
Nicolas Zea 2012/01/18 20:40:42 No, I expect it to return the first fully synced i
148 entry.Get(syncable::SERVER_PARENT_ID));
rlarocque 2012/01/10 23:39:51 SERVER_PARENT_ID? Did you mean SERVER_PREV_ID?
Nicolas Zea 2012/01/18 02:54:39 No, you pass the parent to ComputePrevIdFromServer
149 bool position_matches = parent_matches &&
150 (server_prev_id == entry.Get(syncable::PREV_ID));
ncarter (slow) 2012/01/04 21:37:59 The comment about 'conservative' could be read to
Nicolas Zea 2012/01/18 02:54:39 Upon further reflection, the code as stands actual
151 DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id "
152 << " is " << server_prev_id << ", local prev id is "
153 << entry.Get(syncable::PREV_ID);
141 const sync_pb::EntitySpecifics& specifics = 154 const sync_pb::EntitySpecifics& specifics =
142 entry.Get(syncable::SPECIFICS); 155 entry.Get(syncable::SPECIFICS);
143 const sync_pb::EntitySpecifics& server_specifics = 156 const sync_pb::EntitySpecifics& server_specifics =
144 entry.Get(syncable::SERVER_SPECIFICS); 157 entry.Get(syncable::SERVER_SPECIFICS);
145 const sync_pb::EntitySpecifics& base_server_specifics = 158 const sync_pb::EntitySpecifics& base_server_specifics =
146 entry.Get(syncable::BASE_SERVER_SPECIFICS); 159 entry.Get(syncable::BASE_SERVER_SPECIFICS);
147 std::string decrypted_specifics, decrypted_server_specifics; 160 std::string decrypted_specifics, decrypted_server_specifics;
148 bool specifics_match = false; 161 bool specifics_match = false;
149 bool server_encrypted_with_default_key = false; 162 bool server_encrypted_with_default_key = false;
150 if (specifics.has_encrypted()) { 163 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 224 // We deliberately leave the server's device information. This client will
212 // add it's own device information on restart. 225 // add it's own device information on restart.
213 entry.Put(syncable::SPECIFICS, specifics); 226 entry.Put(syncable::SPECIFICS, specifics);
214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; 227 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry;
215 status->increment_num_server_overwrites(); 228 status->increment_num_server_overwrites();
216 OverwriteServerChanges(trans, &entry); 229 OverwriteServerChanges(trans, &entry);
217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 230 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
218 NIGORI_MERGE, 231 NIGORI_MERGE,
219 CONFLICT_RESOLUTION_SIZE); 232 CONFLICT_RESOLUTION_SIZE);
220 } else if (!entry_deleted && name_matches && parent_matches && 233 } else if (!entry_deleted && name_matches && parent_matches &&
221 specifics_match) { 234 position_matches && specifics_match) {
222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " 235 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring "
223 << "changes for: " << entry; 236 << "changes for: " << entry;
224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the 237 // 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 238 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset
226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data 239 // 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 240 // 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 241 // re-inserted into the PREV_ID/NEXT_ID linked list.
229 // positional info).
230 OverwriteServerChanges(trans, &entry); 242 OverwriteServerChanges(trans, &entry);
231 IgnoreLocalChanges(&entry); 243 IgnoreLocalChanges(&entry);
232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 244 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
233 CHANGES_MATCH, 245 CHANGES_MATCH,
234 CONFLICT_RESOLUTION_SIZE); 246 CONFLICT_RESOLUTION_SIZE);
235 } else if (base_server_specifics_match) { 247 } else if (base_server_specifics_match) {
236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " 248 DVLOG(1) << "Resolving simple conflict, ignoring server encryption "
237 << " changes for: " << entry; 249 << " changes for: " << entry;
238 status->increment_num_server_overwrites(); 250 status->increment_num_server_overwrites();
239 OverwriteServerChanges(trans, &entry); 251 OverwriteServerChanges(trans, &entry);
240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 252 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
241 IGNORE_ENCRYPTION, 253 IGNORE_ENCRYPTION,
242 CONFLICT_RESOLUTION_SIZE); 254 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) { 255 } else if (entry_deleted || !name_matches || !parent_matches) {
rlarocque 2012/01/10 23:39:51 Assertion: If this condition is true, then we woul
Nicolas Zea 2012/01/18 02:54:39 I'm not sure I understand this assertion. The if s
247 OverwriteServerChanges(trans, &entry); 256 OverwriteServerChanges(trans, &entry);
248 status->increment_num_server_overwrites(); 257 status->increment_num_server_overwrites();
249 DVLOG(1) << "Resolving simple conflict, overwriting server changes " 258 DVLOG(1) << "Resolving simple conflict, overwriting server changes "
250 << "for: " << entry; 259 << "for: " << entry;
251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 260 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
252 OVERWRITE_SERVER, 261 OVERWRITE_SERVER,
253 CONFLICT_RESOLUTION_SIZE); 262 CONFLICT_RESOLUTION_SIZE);
254 } else { 263 } else {
rlarocque 2012/01/10 23:39:51 I believe this is where your change on line 234 st
Nicolas Zea 2012/01/18 02:54:39 See above comment. When the position doesn't match
rlarocque 2012/01/18 20:21:55 As I understand it, most conflicts end up being re
Nicolas Zea 2012/01/18 20:40:42 Okay, I think we're in agreement here then. Yes, t
255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " 264 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: "
256 << entry; 265 << entry;
257 IgnoreLocalChanges(&entry); 266 IgnoreLocalChanges(&entry);
258 status->increment_num_local_overwrites(); 267 status->increment_num_local_overwrites();
259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", 268 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict",
260 OVERWRITE_LOCAL, 269 OVERWRITE_LOCAL,
261 CONFLICT_RESOLUTION_SIZE); 270 CONFLICT_RESOLUTION_SIZE);
262 } 271 }
272 // Now that we've resolved the conflict, clear the prev server
273 // specifics.
274 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics());
263 return SYNC_PROGRESS; 275 return SYNC_PROGRESS;
264 } else { // SERVER_IS_DEL is true 276 } else { // SERVER_IS_DEL is true
265 // If a server deleted folder has local contents we should be in a set. 277 // If a server deleted folder has local contents we should be in a set.
266 if (entry.Get(syncable::IS_DIR)) { 278 if (entry.Get(syncable::IS_DIR)) {
267 Directory::ChildHandles children; 279 Directory::ChildHandles children;
268 trans->directory()->GetChildHandlesById(trans, 280 trans->directory()->GetChildHandlesById(trans,
269 entry.Get(syncable::ID), 281 entry.Get(syncable::ID),
270 &children); 282 &children);
271 if (0 != children.size()) { 283 if (0 != children.size()) {
272 DVLOG(1) << "Entry is a server deleted directory with local contents, " 284 DVLOG(1) << "Entry is a server deleted directory with local contents, "
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
311 } 323 }
312 324
313 bool ConflictResolver::ResolveSimpleConflicts( 325 bool ConflictResolver::ResolveSimpleConflicts(
314 syncable::WriteTransaction* trans, 326 syncable::WriteTransaction* trans,
315 const Cryptographer* cryptographer, 327 const Cryptographer* cryptographer,
316 const ConflictProgress& progress, 328 const ConflictProgress& progress,
317 sessions::StatusController* status) { 329 sessions::StatusController* status) {
318 bool forward_progress = false; 330 bool forward_progress = false;
319 // First iterate over simple conflict items (those that belong to no set). 331 // First iterate over simple conflict items (those that belong to no set).
320 set<Id>::const_iterator conflicting_item_it; 332 set<Id>::const_iterator conflicting_item_it;
333 set<Id> processed_items;
321 for (conflicting_item_it = progress.ConflictingItemsBegin(); 334 for (conflicting_item_it = progress.ConflictingItemsBegin();
322 conflicting_item_it != progress.ConflictingItemsEnd(); 335 conflicting_item_it != progress.ConflictingItemsEnd();
323 ++conflicting_item_it) { 336 ++conflicting_item_it) {
324 Id id = *conflicting_item_it; 337 Id id = *conflicting_item_it;
338 if (processed_items.count(id) > 0)
339 continue;
325 map<Id, ConflictSet*>::const_iterator item_set_it = 340 map<Id, ConflictSet*>::const_iterator item_set_it =
326 progress.IdToConflictSetFind(id); 341 progress.IdToConflictSetFind(id);
327 if (item_set_it == progress.IdToConflictSetEnd() || 342 if (item_set_it == progress.IdToConflictSetEnd() ||
328 0 == item_set_it->second) { 343 0 == item_set_it->second) {
329 // We have a simple conflict. 344 // We have a simple conflict. In order check if positions have changed,
rlarocque 2012/01/18 20:21:55 I don't understand why this is necessary. I think
Nicolas Zea 2012/01/18 20:40:42 You're right, we don't modify anything except IS_
330 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { 345 // we need to process conflicting predecessors before successors. Traverse
331 case NO_SYNC_PROGRESS: 346 // backwards through all continuous conflicting predecessors, building a
347 // stack of items to resolve in predecessor->successor order, then process
348 // each item individually.
349 list<Id> predecessors;
350 Id prev_id = id;
351 do {
352 predecessors.push_back(prev_id);
353 Entry entry(trans, syncable::GET_BY_ID, prev_id);
354 // Any entry in conflict must be valid.
355 CHECK(entry.good());
356 Id new_prev_id = entry.Get(syncable::PREV_ID);
357 if (new_prev_id == prev_id)
332 break; 358 break;
333 case SYNC_PROGRESS: 359 prev_id = new_prev_id;
334 forward_progress = true; 360 } while (processed_items.count(prev_id) == 0 &&
335 break; 361 progress.HasSimpleConflictItem(prev_id)); // Excludes root.
362 while (!predecessors.empty()) {
363 id = predecessors.back();
364 predecessors.pop_back();
365 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) {
366 case NO_SYNC_PROGRESS:
367 break;
368 case SYNC_PROGRESS:
369 forward_progress = true;
370 break;
371 }
372 processed_items.insert(id);
336 } 373 }
337 } 374 }
338 } 375 }
339 return forward_progress; 376 return forward_progress;
340 } 377 }
341 378
342 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, 379 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans,
343 const Cryptographer* cryptographer, 380 const Cryptographer* cryptographer,
344 const ConflictProgress& progress, 381 const ConflictProgress& progress,
345 sessions::StatusController* status) { 382 sessions::StatusController* status) {
346 // TODO(rlarocque): A good amount of code related to the resolution of 383 // 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 384 // 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 385 // 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. 386 // 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 387 // 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 388 // 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. 389 // that code was trying to do and consider re-enabling parts of it.
353 390
354 if (progress.ConflictSetsSize() > 0) { 391 if (progress.ConflictSetsSize() > 0) {
355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() 392 DVLOG(1) << "Detected " << progress.IdToConflictSetSize()
356 << " non-simple conflicting entries in " << progress.ConflictSetsSize() 393 << " non-simple conflicting entries in " << progress.ConflictSetsSize()
357 << " unprocessed conflict sets."; 394 << " unprocessed conflict sets.";
358 } 395 }
359 396
360 return ResolveSimpleConflicts(trans, cryptographer, progress, status); 397 return ResolveSimpleConflicts(trans, cryptographer, progress, status);
361 } 398 }
362 399
363 } // namespace browser_sync 400 } // namespace browser_sync
OLDNEW
« no previous file with comments | « chrome/browser/sync/engine/build_commit_command.cc ('k') | chrome/browser/sync/engine/get_commit_ids_command.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698