|
OLD | NEW |
---|---|
(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 | |
OLD | NEW |