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

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

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 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
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2009 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 entry.
4
5 #include "chrome/browser/sync/engine/conflict_resolver.h"
6
7 #include <map>
8 #include <set>
9
10 #include "chrome/browser/sync/engine/syncer.h"
11 #include "chrome/browser/sync/engine/syncer_util.h"
12 #include "chrome/browser/sync/protocol/service_constants.h"
13 #include "chrome/browser/sync/syncable/directory_manager.h"
14 #include "chrome/browser/sync/syncable/syncable.h"
15 #include "chrome/browser/sync/util/character_set_converters.h"
16 #include "chrome/browser/sync/util/event_sys-inl.h"
17 #include "chrome/browser/sync/util/path_helpers.h"
18
19 using std::map;
20 using std::set;
21 using syncable::BaseTransaction;
22 using syncable::Directory;
23 using syncable::Entry;
24 using syncable::Id;
25 using syncable::MutableEntry;
26 using syncable::Name;
27 using syncable::ScopedDirLookup;
28 using syncable::SyncName;
29 using syncable::WriteTransaction;
30
31 namespace browser_sync {
32
33 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8;
34
35 ConflictResolver::ConflictResolver() {
36 }
37
38 ConflictResolver::~ConflictResolver() {
39 }
40
41 namespace {
42 // TODO(ncarter): Remove title/path conflicts and the code to resolve them.
43 // This is historical cruft that seems to be actually reached by some users.
44 inline PathString GetConflictPathnameBase(PathString base) {
45 time_t time_since = time(NULL);
46 struct tm* now = localtime(&time_since);
47 // Use a fixed format as the locale's format may include '/' characters or
48 // other illegal characters.
49 PathString date = IntToPathString(now->tm_year + 1900);
50 date.append(PSTR("-"));
51 ++now->tm_mon; // tm_mon is 0-based.
52 if (now->tm_mon < 10)
53 date.append(PSTR("0"));
54 date.append(IntToPathString(now->tm_mon));
55 date.append(PSTR("-"));
56 if (now->tm_mday < 10)
57 date.append(PSTR("0"));
58 date.append(IntToPathString(now->tm_mday));
59 return base + PSTR(" (Edited on ") + date + PSTR(")");
60 }
61
62 // TODO(ncarter): Remove title/path conflicts and the code to resolve them.
63 Name FindNewName(BaseTransaction* trans,
64 Id parent_id,
65 const SyncName& original_name) {
66 const PathString name = original_name.value();
67 // 255 is defined in our spec.
68 const int allowed_length = kSyncProtocolMaxNameLengthBytes;
69 // TODO(sync): How do we get length on other platforms? The limit is
70 // checked in java on the server, so it's not the number of glyphs its the
71 // number of 16 bit characters in the UTF-16 representation.
72
73 // 10 characters for 32 bit numbers + 2 characters for brackets means 12
74 // characters should be more than enough for the name. Doubling this ensures
75 // that we will have enough space.
76 COMPILE_ASSERT(kSyncProtocolMaxNameLengthBytes >= 24,
77 maximum_name_too_short);
78 CHECK(name.length() <= allowed_length);
79
80 if (!Entry(trans,
81 syncable::GET_BY_PARENTID_AND_DBNAME,
82 parent_id,
83 name).good())
84 return Name::FromSyncName(original_name);
85 PathString base = name;
86 PathString ext;
87 PathString::size_type ext_index = name.rfind('.');
88 if (PathString::npos != ext_index && 0 != ext_index &&
89 name.length() - ext_index < allowed_length / 2) {
90 base = name.substr(0, ext_index);
91 ext = name.substr(ext_index);
92 }
93
94 PathString name_base = GetConflictPathnameBase(base);
95 if (name_base.length() + ext.length() > allowed_length) {
96 name_base.resize(allowed_length - ext.length());
97 TrimPathStringToValidCharacter(&name_base);
98 }
99 PathString new_name = name_base + ext;
100 int n = 2;
101 while (Entry(trans,
102 syncable::GET_BY_PARENTID_AND_DBNAME,
103 parent_id,
104 new_name).good()) {
105 PathString local_ext = PSTR("(");
106 local_ext.append(IntToPathString(n));
107 local_ext.append(PSTR(")"));
108 local_ext.append(ext);
109 if (name_base.length() + local_ext.length() > allowed_length) {
110 name_base.resize(allowed_length - local_ext.length());
111 TrimPathStringToValidCharacter(&name_base);
112 }
113 new_name = name_base + local_ext;
114 n++;
115 }
116
117 CHECK(new_name.length() <= kSyncProtocolMaxNameLengthBytes);
118 return Name(new_name);
119 }
120
121 } // namespace
122
123 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) {
124 // An update matches local actions, merge the changes.
125 // This is a little fishy because we don't actually merge them.
126 // In the future we should do a 3-way merge.
127 LOG(INFO) << "Server and local changes match, merging:" << entry;
128 // With IS_UNSYNCED false, changes should be merged.
129 // METRIC simple conflict resolved by merge.
130 entry->Put(syncable::IS_UNSYNCED, false);
131 }
132
133 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans,
134 MutableEntry * entry) {
idana 2009/09/10 05:44:37 nit: "MutableEntry * entry" -> "MutableEntry* entr
135 // This is similar to an overwrite from the old client.
136 // This is equivalent to a scenario where we got the update before we'd
137 // made our local client changes.
138 // TODO(chron): This is really a general property clobber. We clobber
139 // the server side property. Perhaps we should actually do property merging.
140 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION));
141 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false);
142 // METRIC conflict resolved by overwrite.
143 }
144
145 ConflictResolver::ProcessSimpleConflictResult
146 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans,
147 Id id,
148 SyncerSession* session) {
149 MutableEntry entry(trans, syncable::GET_BY_ID, id);
150 // Must be good as the entry won't have been cleaned up.
151 CHECK(entry.good());
152 // If an update fails, locally we have to be in a set or unsynced. We're not
153 // in a set here, so we must be unsynced.
154 if (!entry.Get(syncable::IS_UNSYNCED))
155 return NO_SYNC_PROGRESS;
156 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE)) {
157 if (!entry.Get(syncable::PARENT_ID).ServerKnows()) {
158 LOG(INFO) << "Item conflicting because its parent not yet committed. "
159 "Id: "<< id;
160 } else {
161 LOG(INFO) << "No set for conflicting entry id " << id << ". There should "
162 "be an update/commit that will fix this soon. This message should "
163 "not repeat.";
164 }
165 return NO_SYNC_PROGRESS;
166 }
167 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) {
168 // we've both deleted it, so lets just drop the need to commit/update this
169 // entry.
170 entry.Put(syncable::IS_UNSYNCED, false);
171 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false);
172 // we've made changes, but they won't help syncing progress.
173 // METRIC simple conflict resolved by merge.
174 return NO_SYNC_PROGRESS;
175 }
176
177 if (!entry.Get(syncable::SERVER_IS_DEL)) {
178 // TODO(chron): Should we check more fields? Since IS_UNSYNCED is
179 // turned on, this is really probably enough as fields will be overwritten.
180 // Check if there's no changes.
181
182 // Verbose but easier to debug.
183 bool name_matches = entry.SyncNameMatchesServerName();
184 bool parent_matches = entry.Get(syncable::PARENT_ID) ==
185 entry.Get(syncable::SERVER_PARENT_ID);
186 bool entry_deleted = entry.Get(syncable::IS_DEL);
187
188 if (!entry_deleted && name_matches && parent_matches) {
189 LOG(INFO) << "Resolving simple conflict, ignoring local changes for:"
190 << entry;
191 IgnoreLocalChanges(&entry);
192 } else {
193 LOG(INFO) << "Resolving simple conflict, overwriting server"
194 " changes for:" << entry;
195 OverwriteServerChanges(trans, &entry);
196 }
197 return SYNC_PROGRESS;
198 } else { // SERVER_IS_DEL is true
199 // If a server deleted folder has local contents we should be in a set.
200 if (entry.Get(syncable::IS_DIR)) {
201 Directory::ChildHandles children;
202 trans->directory()->GetChildHandles(trans,
203 entry.Get(syncable::ID),
204 &children);
205 if (0 != children.size()) {
206 LOG(INFO) << "Entry is a server deleted directory with local contents, "
207 "should be in a set. (race condition).";
208 return NO_SYNC_PROGRESS;
209 }
210 }
211 // METRIC conflict resolved by entry split;
212
213 // If the entry's deleted on the server, we can have a directory here.
214 entry.Put(syncable::IS_UNSYNCED, true);
215
216 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry);
217
218 MutableEntry server_update(trans, syncable::GET_BY_ID, id);
219 CHECK(server_update.good());
220 CHECK(server_update.Get(syncable::META_HANDLE) !=
221 entry.Get(syncable::META_HANDLE))
222 << server_update << entry;
223
224 return SYNC_PROGRESS;
225 }
226 }
227
228 namespace {
229
230 bool NamesCollideWithChildrenOfFolder(BaseTransaction* trans,
231 const Directory::ChildHandles& children,
232 Id folder_id) {
233 Directory::ChildHandles::const_iterator i = children.begin();
234 while (i != children.end()) {
235 Entry child(trans, syncable::GET_BY_HANDLE, *i);
236 CHECK(child.good());
237 if (Entry(trans,
238 syncable::GET_BY_PARENTID_AND_DBNAME,
239 folder_id,
240 child.GetName().db_value()).good())
241 return true;
242 ++i;
243 }
244 return false;
245 }
246
247 void GiveEntryNewName(WriteTransaction* trans,
248 MutableEntry* entry) {
249 using namespace syncable;
250 Name new_name =
251 FindNewName(trans, entry->Get(syncable::PARENT_ID), entry->GetName());
252 LOG(INFO) << "Resolving name clash, renaming " << *entry << " to "
253 << new_name.db_value();
254 entry->PutName(new_name);
255 CHECK(entry->Get(syncable::IS_UNSYNCED));
256 }
257
258 } // namespace
259
260 bool ConflictResolver::AttemptItemMerge(WriteTransaction* trans,
261 MutableEntry* locally_named,
262 MutableEntry* server_named) {
263 // To avoid complications we only merge new entries with server entries.
264 if (locally_named->Get(syncable::IS_DIR) !=
265 server_named->Get(syncable::SERVER_IS_DIR) ||
266 locally_named->Get(syncable::ID).ServerKnows() ||
267 locally_named->Get(syncable::IS_UNAPPLIED_UPDATE) ||
268 server_named->Get(syncable::IS_UNSYNCED))
269 return false;
270 Id local_id = locally_named->Get(syncable::ID);
271 Id desired_id = server_named->Get(syncable::ID);
272 if (locally_named->Get(syncable::IS_DIR)) {
273 // Extra work for directory name clash. We have to make sure we don't have
274 // clashing child items, and update the parent id the children of the new
275 // entry.
276 Directory::ChildHandles children;
277 trans->directory()->GetChildHandles(trans, local_id, &children);
278 if (NamesCollideWithChildrenOfFolder(trans, children, desired_id))
279 return false;
280
281 LOG(INFO) << "Merging local changes to: " << desired_id << ". "
282 << *locally_named;
283
284 server_named->Put(syncable::ID, trans->directory()->NextId());
285 Directory::ChildHandles::iterator i;
286 for (i = children.begin() ; i != children.end() ; ++i) {
287 MutableEntry child_entry(trans, syncable::GET_BY_HANDLE, *i);
288 CHECK(child_entry.good());
289 CHECK(child_entry.Put(syncable::PARENT_ID, desired_id));
290 CHECK(child_entry.Put(syncable::IS_UNSYNCED, true));
291 Id id = child_entry.Get(syncable::ID);
292 // we only note new entries for quicker merging next round.
293 if (!id.ServerKnows())
294 children_of_merged_dirs_.insert(id);
295 }
296 } else {
297 if (!server_named->Get(syncable::IS_DEL))
298 return false;
299 }
300
301 LOG(INFO) << "Identical client and server items merging server changes. " <<
302 *locally_named << " server: " << *server_named;
303
304 // Clear server_named's server data and mark it deleted so it goes away
305 // quietly because it's now identical to a deleted local entry.
306 // locally_named takes on the ID of the server entry.
307 server_named->Put(syncable::ID, trans->directory()->NextId());
308 locally_named->Put(syncable::ID, desired_id);
309 locally_named->Put(syncable::IS_UNSYNCED, false);
310 CopyServerFields(server_named, locally_named);
311 ClearServerData(server_named);
312 server_named->Put(syncable::IS_DEL, true);
313 server_named->Put(syncable::BASE_VERSION, 0);
314 CHECK(SUCCESS ==
315 SyncerUtil::AttemptToUpdateEntryWithoutMerge(
316 trans, locally_named, NULL, NULL));
317 return true;
318 }
319
320 ConflictResolver::ServerClientNameClashReturn
321 ConflictResolver::ProcessServerClientNameClash(WriteTransaction* trans,
322 MutableEntry* locally_named,
323 MutableEntry* server_named,
324 SyncerSession* session) {
325 if (!locally_named->ExistsOnClientBecauseDatabaseNameIsNonEmpty())
326 return NO_CLASH; // locally_named is a server update.
327 if (locally_named->Get(syncable::IS_DEL) ||
328 server_named->Get(syncable::SERVER_IS_DEL)) {
329 return NO_CLASH;
330 }
331 if (locally_named->Get(syncable::PARENT_ID) !=
332 server_named->Get(syncable::SERVER_PARENT_ID)) {
333 return NO_CLASH; // different parents
334 }
335
336 PathString name = locally_named->GetSyncNameValue();
337 if (0 != syncable::ComparePathNames(name,
338 server_named->Get(syncable::SERVER_NAME))) {
339 return NO_CLASH; // different names.
340 }
341
342 // First try to merge.
343 if (AttemptItemMerge(trans, locally_named, server_named)) {
344 // METRIC conflict resolved by merge
345 return SOLVED;
346 }
347 // We need to rename.
348 if (!locally_named->Get(syncable::IS_UNSYNCED)) {
349 LOG(ERROR) << "Locally named part of a name conflict not unsynced?";
350 locally_named->Put(syncable::IS_UNSYNCED, true);
351 }
352 if (!server_named->Get(syncable::IS_UNAPPLIED_UPDATE)) {
353 LOG(ERROR) << "Server named part of a name conflict not an update?";
354 }
355 GiveEntryNewName(trans, locally_named);
356
357 // METRIC conflict resolved by rename
358 return SOLVED;
359 }
360
361 ConflictResolver::ServerClientNameClashReturn
362 ConflictResolver::ProcessNameClashesInSet(WriteTransaction* trans,
363 ConflictSet* conflict_set,
364 SyncerSession* session) {
365 ConflictSet::const_iterator i,j;
366 for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) {
367 MutableEntry entryi(trans, syncable::GET_BY_ID, *i);
368 if (!entryi.Get(syncable::IS_UNSYNCED) &&
369 !entryi.Get(syncable::IS_UNAPPLIED_UPDATE))
370 // This set is broken / doesn't make sense, this may be transient.
371 return BOGUS_SET;
372 for (j = conflict_set->begin() ; *i != *j ; ++j) {
373 MutableEntry entryj(trans, syncable::GET_BY_ID, *j);
374 ServerClientNameClashReturn rv =
375 ProcessServerClientNameClash(trans, &entryi, &entryj, session);
376 if (NO_CLASH == rv)
377 rv = ProcessServerClientNameClash(trans, &entryj, &entryi, session);
378 if (NO_CLASH != rv)
379 return rv;
380 }
381 }
382 return NO_CLASH;
383 }
384
385 ConflictResolver::ConflictSetCountMapKey ConflictResolver::GetSetKey(
386 ConflictSet* set) {
387 // TODO(sync): Come up with a better scheme for set hashing. This scheme
388 // will make debugging easy.
389 // If this call to sort is removed, we need to add one before we use
390 // binary_search in ProcessConflictSet
391 sort(set->begin(), set->end());
392 std::stringstream rv;
393 for(ConflictSet::iterator i = set->begin() ; i != set->end() ; ++i )
394 rv << *i << ".";
395 return rv.str();
396 }
397
398 namespace {
399
400 bool AttemptToFixCircularConflict(WriteTransaction* trans,
401 ConflictSet* conflict_set) {
402 ConflictSet::const_iterator i, j;
403 for(i = conflict_set->begin() ; i != conflict_set->end() ; ++i) {
404 MutableEntry entryi(trans, syncable::GET_BY_ID, *i);
405 if (entryi.Get(syncable::PARENT_ID) ==
406 entryi.Get(syncable::SERVER_PARENT_ID) ||
407 !entryi.Get(syncable::IS_UNAPPLIED_UPDATE) ||
408 !entryi.Get(syncable::IS_DIR)) {
409 continue;
410 }
411 Id parentid = entryi.Get(syncable::SERVER_PARENT_ID);
412 // Create the entry here as it's the only place we could ever get a parentid
413 // that doesn't correspond to a real entry.
414 Entry parent(trans, syncable::GET_BY_ID, parentid);
415 if (!parent.good()) // server parent update not received yet
416 continue;
417 // This loop walks upwards from the server parent. If we hit the root (0)
418 // all is well. If we hit the entry we're examining it means applying the
419 // parent id would cause a loop. We don't need more general loop detection
420 // because we know our local tree is valid.
421 while (!parentid.IsRoot()) {
422 Entry parent(trans, syncable::GET_BY_ID, parentid);
423 CHECK(parent.good());
424 if (parentid == *i)
425 break; // it's a loop
426 parentid = parent.Get(syncable::PARENT_ID);
427 }
428 if (parentid.IsRoot())
429 continue;
430 LOG(INFO) << "Overwriting server changes to avoid loop: " << entryi;
431 entryi.Put(syncable::BASE_VERSION, entryi.Get(syncable::SERVER_VERSION));
432 entryi.Put(syncable::IS_UNSYNCED, true);
433 entryi.Put(syncable::IS_UNAPPLIED_UPDATE, false);
434 // METRIC conflict resolved by breaking dir loop.
435 return true;
436 }
437 return false;
438 }
439
440 bool AttemptToFixUnsyncedEntryInDeletedServerTree(WriteTransaction* trans,
441 ConflictSet* conflict_set,
442 const Entry& entry) {
443 if (!entry.Get(syncable::IS_UNSYNCED) || entry.Get(syncable::IS_DEL))
444 return false;
445 Id parentid = entry.Get(syncable::PARENT_ID);
446 MutableEntry parent(trans, syncable::GET_BY_ID, parentid);
447 if (!parent.good() || !parent.Get(syncable::IS_UNAPPLIED_UPDATE) ||
448 !parent.Get(syncable::SERVER_IS_DEL) ||
449 !binary_search(conflict_set->begin(), conflict_set->end(), parentid))
450 return false;
451 // We're trying to commit into a directory tree that's been deleted.
452 // To solve this we recreate the directory tree.
453 //
454 // We do this in two parts, first we ensure the tree is unaltered since the
455 // conflict was detected.
456 Id id = parentid;
457 while (!id.IsRoot()) {
458 if (!binary_search(conflict_set->begin(), conflict_set->end(), id))
459 break;
460 Entry parent(trans, syncable::GET_BY_ID, id);
461 if (!parent.good() || !parent.Get(syncable::IS_UNAPPLIED_UPDATE) ||
462 !parent.Get(syncable::SERVER_IS_DEL))
463 return false;
464 id = parent.Get(syncable::PARENT_ID);
465 }
466 // Now we fix up the entries.
467 id = parentid;
468 while (!id.IsRoot()) {
469 MutableEntry parent(trans, syncable::GET_BY_ID, id);
470 if (!binary_search(conflict_set->begin(), conflict_set->end(), id))
471 break;
472 LOG(INFO) << "Giving directory a new id so we can undelete it "
473 << parent;
474 ClearServerData(&parent);
475 SyncerUtil::ChangeEntryIDAndUpdateChildren(trans, &parent,
476 trans->directory()->NextId());
477 parent.Put(syncable::BASE_VERSION, 0);
478 parent.Put(syncable::IS_UNSYNCED, true);
479 id = parent.Get(syncable::PARENT_ID);
480 // METRIC conflict resolved by recreating dir tree.
481 }
482 return true;
483 }
484
485 bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans,
486 ConflictSet* conflict_set,
487 const Entry& entry) {
488 if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) ||
489 entry.Get(syncable::SERVER_IS_DEL))
490 return false;
491 Id parent_id = entry.Get(syncable::SERVER_PARENT_ID);
492 MutableEntry parent(trans, syncable::GET_BY_ID, parent_id);
493 if (!parent.good() || !parent.Get(syncable::IS_DEL) ||
494 !binary_search(conflict_set->begin(), conflict_set->end(), parent_id)) {
495 return false;
496 }
497 // We've deleted a directory tree that's got contents on the server.
498 // We recreate the directory to solve the problem.
499 //
500 // We do this in two parts, first we ensure the tree is unaltered since
501 // the conflict was detected.
502 Id id = parent_id;
503 // As we will be crawling the path of deleted entries there's a chance
504 // we'll end up having to reparent an item as there will be an invalid
505 // parent.
506 Id reroot_id = syncable::kNullId;
507 // similarly crawling deleted items means we risk loops.
508 int loop_detection = conflict_set->size();
509 while (!id.IsRoot() && --loop_detection >= 0) {
510 Entry parent(trans, syncable::GET_BY_ID, id);
511 // If we get a bad parent, or a parent that's deleted on client and
512 // server we recreate the hierarchy in the root.
513 if (!parent.good()) {
514 reroot_id = id;
515 break;
516 }
517 CHECK(parent.Get(syncable::IS_DIR));
518 if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) {
519 // We've got to an entry that's not in the set. If it has been
520 // deleted between set building and this point in time we
521 // return false. If it had been deleted earlier it would have been
522 // in the set.
523 // TODO(sync): Revisit syncer code organization to see if
524 // conflict resolution can be done in the same transaction as set
525 // building.
526 if (parent.Get(syncable::IS_DEL))
527 return false;
528 break;
529 }
530 if (!parent.Get(syncable::IS_DEL) ||
531 parent.Get(syncable::SERVER_IS_DEL) ||
532 !parent.Get(syncable::IS_UNSYNCED)) {
533 return false;
534 }
535 id = parent.Get(syncable::PARENT_ID);
536 }
537 // If we find we've been looping we re-root the hierarchy.
538 if (loop_detection < 0)
539 if (id == entry.Get(syncable::ID))
540 reroot_id = entry.Get(syncable::PARENT_ID);
541 else
542 reroot_id = id;
543 // Now we fix things up by undeleting all the folders in the item's
544 // path.
545 id = parent_id;
546 while (!id.IsRoot() && id != reroot_id) {
547 if (!binary_search(conflict_set->begin(), conflict_set->end(), id))
548 break;
549 MutableEntry entry(trans, syncable::GET_BY_ID, id);
550 Id parent_id = entry.Get(syncable::PARENT_ID);
551 if (parent_id == reroot_id)
552 parent_id = trans->root_id();
553 Name old_name = entry.GetName();
554 Name new_name = FindNewName(trans, parent_id, old_name);
555 LOG(INFO) << "Undoing our deletion of " << entry <<
556 ", will have name " << new_name.db_value();
557 if (new_name != old_name || parent_id != entry.Get(syncable::PARENT_ID))
558 CHECK(entry.PutParentIdAndName(parent_id, new_name));
559 entry.Put(syncable::IS_DEL, false);
560 id = entry.Get(syncable::PARENT_ID);
561 // METRIC conflict resolved by recreating dir tree.
562 }
563 return true;
564 }
565
566 bool AttemptToFixRemovedDirectoriesWithContent(WriteTransaction* trans,
567 ConflictSet* conflict_set) {
568 ConflictSet::const_iterator i,j;
569 for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) {
570 Entry entry(trans, syncable::GET_BY_ID, *i);
571 if (AttemptToFixUnsyncedEntryInDeletedServerTree(trans,
572 conflict_set, entry)) {
573 return true;
574 }
575 if (AttemptToFixUpdateEntryInDeletedLocalTree(trans, conflict_set, entry))
576 return true;
577 }
578 return false;
579 }
580
581 } // namespace
582
583 bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans,
584 ConflictSet* conflict_set,
585 int conflict_count,
586 SyncerSession* session) {
587 int set_size = conflict_set->size();
588 if (set_size < 2) {
589 LOG(WARNING) << "Skipping conflict set because it has size " << set_size;
590 // We can end up with sets of size one if we have a new item in a set that
591 // we tried to commit transactionally. This should not be a persistent
592 // situation.
593 return false;
594 }
595 if (conflict_count < 3) {
596 // Avoid resolving sets that could be the result of transient conflicts.
597 // Transient conflicts can occur because the client or server can be
598 // slightly out of date.
599 return false;
600 }
601
602 LOG(INFO) << "Fixing a set containing " << set_size << " items";
603
604 ServerClientNameClashReturn rv = ProcessNameClashesInSet(trans, conflict_set,
605 session);
606 if (SOLVED == rv)
607 return true;
608 if (NO_CLASH != rv)
609 return false;
610
611 // Fix circular conflicts.
612 if (AttemptToFixCircularConflict(trans, conflict_set))
613 return true;
614 // Check for problems involving contents of removed folders.
615 if (AttemptToFixRemovedDirectoriesWithContent(trans, conflict_set))
616 return true;
617 return false;
618 }
619
620
idana 2009/09/10 05:44:37 Please remove extra blank line.
621 template <typename InputIt>
622 bool ConflictResolver::LogAndSignalIfConflictStuck(
623 BaseTransaction* trans,
624 int attempt_count,
625 InputIt begin,
626 InputIt end,
627 ConflictResolutionView* view) {
628 if (attempt_count < SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT)
629 return false;
630
631 // Don't signal stuck if we're not up to date.
632 if (view->servers_latest_timestamp() != view->current_sync_timestamp())
633 return false;
634
635 LOG(ERROR) << "[BUG] Conflict set cannot be resolved, has "
636 << end - begin << " items:";
637 for (InputIt i = begin ; i != end ; ++i) {
638 Entry e(trans, syncable::GET_BY_ID, *i);
639 if (e.good())
640 LOG(ERROR) << " " << e;
641 else
642 LOG(ERROR) << " Bad ID:" << *i;
643 }
644
645 view->set_syncer_stuck(true);
646
647 return true;
648 // TODO(sync): If we're stuck for a while we need to alert the user,
649 // clear cache or reset syncing. At the very least we should stop trying
650 // something that's obviously not working.
651 }
652
653 bool ConflictResolver::ResolveSimpleConflicts(const ScopedDirLookup& dir,
654 ConflictResolutionView* view,
655 SyncerSession *session) {
idana 2009/09/10 05:44:37 "SyncerSession *session" -> "SyncerSession* sessio
656 WriteTransaction trans(dir, syncable::SYNCER, __FILE__, __LINE__);
657 bool forward_progress = false;
658 // First iterate over simple conflict items (those that belong to no set).
659 set<Id>::const_iterator conflicting_item_it;
660 for (conflicting_item_it = view->CommitConflictsBegin();
661 conflicting_item_it != view->CommitConflictsEnd() ;
662 ++conflicting_item_it) {
663 Id id = *conflicting_item_it;
664 map<Id, ConflictSet*>::const_iterator item_set_it =
665 view->IdToConflictSetFind(id);
666 if (item_set_it == view->IdToConflictSetEnd() ||
667 0 == item_set_it->second) {
668 // We have a simple conflict.
669 switch(ProcessSimpleConflict(&trans, id, session)) {
670 case NO_SYNC_PROGRESS:
671 {
672 int conflict_count = (simple_conflict_count_map_[id] += 2);
673 bool stuck = LogAndSignalIfConflictStuck(&trans, conflict_count,
674 &id, &id + 1, view);
675 break;
676 }
677 case SYNC_PROGRESS:
678 forward_progress = true;
679 break;
680 }
681 }
682 }
683 // Reduce the simple_conflict_count for each item currently tracked.
684 SimpleConflictCountMap::iterator i = simple_conflict_count_map_.begin();
685 while (i != simple_conflict_count_map_.end()) {
686 if (0 == --(i->second))
687 simple_conflict_count_map_.erase(i++);
688 else
689 ++i;
690 }
691 return forward_progress;
692 }
693
694 bool ConflictResolver::ResolveConflicts(const ScopedDirLookup& dir,
695 ConflictResolutionView* view,
696 SyncerSession *session) {
idana 2009/09/10 05:44:37 "SyncerSession *session" -> "SyncerSession* sessio
697 if (view->HasBlockedItems()) {
698 LOG(INFO) << "Delaying conflict resolution, have " <<
699 view->BlockedItemsSize() << " blocked items.";
700 return false;
701 }
702 bool rv = false;
703 if (ResolveSimpleConflicts(dir, view, session))
704 rv = true;
705 WriteTransaction trans(dir, syncable::SYNCER, __FILE__, __LINE__);
706 set<Id> children_of_dirs_merged_last_round;
707 std::swap(children_of_merged_dirs_, children_of_dirs_merged_last_round);
708 set<ConflictSet*>::const_iterator set_it;
709 for (set_it = view->ConflictSetsBegin();
710 set_it != view->ConflictSetsEnd();
711 set_it++) {
712 ConflictSet* conflict_set = *set_it;
713 ConflictSetCountMapKey key = GetSetKey(conflict_set);
714 conflict_set_count_map_[key] += 2;
715 int conflict_count = conflict_set_count_map_[key];
716 // Keep a metric for new sets.
717 if (2 == conflict_count) {
718 // METRIC conflict sets seen ++
719 }
720 // See if this set contains entries whose parents were merged last round.
721 if (SortedCollectionsIntersect(children_of_dirs_merged_last_round.begin(),
722 children_of_dirs_merged_last_round.end(),
723 conflict_set->begin(),
724 conflict_set->end())) {
725 LOG(INFO) << "Accelerating resolution for hierarchical merge.";
726 conflict_count += 2;
727 }
728 // See if we should process this set.
729 if (ProcessConflictSet(&trans, conflict_set, conflict_count, session)) {
730 rv = true;
731 }
732 SyncerStatus status(session);
733 bool stuck = LogAndSignalIfConflictStuck(&trans, conflict_count,
734 conflict_set->begin(),
735 conflict_set->end(), view);
736 }
737 if (rv) {
738 // This code means we don't signal that syncing is stuck when any conflict
739 // resolution has occured.
740 // TODO(sync): As this will also reduce our sensitivity to problem
741 // conditions and increase the time for cascading resolutions we may have to
742 // revisit this code later, doing something more intelligent.
743 conflict_set_count_map_.clear();
744 simple_conflict_count_map_.clear();
745 }
746 ConflictSetCountMap::iterator i = conflict_set_count_map_.begin();
747 while (i != conflict_set_count_map_.end()) {
748 if (0 == --i->second) {
749 conflict_set_count_map_.erase(i++);
750 // METRIC self resolved conflict sets ++.
751 } else {
752 ++i;
753 }
754 }
755 return rv;
756 }
757
758 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698