Index: chrome/browser/sync/engine/conflict_resolver.cc |
=================================================================== |
--- chrome/browser/sync/engine/conflict_resolver.cc (revision 0) |
+++ chrome/browser/sync/engine/conflict_resolver.cc (revision 0) |
@@ -0,0 +1,758 @@ |
+// Copyright (c) 2009 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE entry. |
+ |
+#include "chrome/browser/sync/engine/conflict_resolver.h" |
+ |
+#include <map> |
+#include <set> |
+ |
+#include "chrome/browser/sync/engine/syncer.h" |
+#include "chrome/browser/sync/engine/syncer_util.h" |
+#include "chrome/browser/sync/protocol/service_constants.h" |
+#include "chrome/browser/sync/syncable/directory_manager.h" |
+#include "chrome/browser/sync/syncable/syncable.h" |
+#include "chrome/browser/sync/util/character_set_converters.h" |
+#include "chrome/browser/sync/util/event_sys-inl.h" |
+#include "chrome/browser/sync/util/path_helpers.h" |
+ |
+using std::map; |
+using std::set; |
+using syncable::BaseTransaction; |
+using syncable::Directory; |
+using syncable::Entry; |
+using syncable::Id; |
+using syncable::MutableEntry; |
+using syncable::Name; |
+using syncable::ScopedDirLookup; |
+using syncable::SyncName; |
+using syncable::WriteTransaction; |
+ |
+namespace browser_sync { |
+ |
+const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; |
+ |
+ConflictResolver::ConflictResolver() { |
+} |
+ |
+ConflictResolver::~ConflictResolver() { |
+} |
+ |
+namespace { |
+// TODO(ncarter): Remove title/path conflicts and the code to resolve them. |
+// This is historical cruft that seems to be actually reached by some users. |
+inline PathString GetConflictPathnameBase(PathString base) { |
+ time_t time_since = time(NULL); |
+ struct tm* now = localtime(&time_since); |
+ // Use a fixed format as the locale's format may include '/' characters or |
+ // other illegal characters. |
+ PathString date = IntToPathString(now->tm_year + 1900); |
+ date.append(PSTR("-")); |
+ ++now->tm_mon; // tm_mon is 0-based. |
+ if (now->tm_mon < 10) |
+ date.append(PSTR("0")); |
+ date.append(IntToPathString(now->tm_mon)); |
+ date.append(PSTR("-")); |
+ if (now->tm_mday < 10) |
+ date.append(PSTR("0")); |
+ date.append(IntToPathString(now->tm_mday)); |
+ return base + PSTR(" (Edited on ") + date + PSTR(")"); |
+} |
+ |
+// TODO(ncarter): Remove title/path conflicts and the code to resolve them. |
+Name FindNewName(BaseTransaction* trans, |
+ Id parent_id, |
+ const SyncName& original_name) { |
+ const PathString name = original_name.value(); |
+ // 255 is defined in our spec. |
+ const int allowed_length = kSyncProtocolMaxNameLengthBytes; |
+ // TODO(sync): How do we get length on other platforms? The limit is |
+ // checked in java on the server, so it's not the number of glyphs its the |
+ // number of 16 bit characters in the UTF-16 representation. |
+ |
+ // 10 characters for 32 bit numbers + 2 characters for brackets means 12 |
+ // characters should be more than enough for the name. Doubling this ensures |
+ // that we will have enough space. |
+ COMPILE_ASSERT(kSyncProtocolMaxNameLengthBytes >= 24, |
+ maximum_name_too_short); |
+ CHECK(name.length() <= allowed_length); |
+ |
+ if (!Entry(trans, |
+ syncable::GET_BY_PARENTID_AND_DBNAME, |
+ parent_id, |
+ name).good()) |
+ return Name::FromSyncName(original_name); |
+ PathString base = name; |
+ PathString ext; |
+ PathString::size_type ext_index = name.rfind('.'); |
+ if (PathString::npos != ext_index && 0 != ext_index && |
+ name.length() - ext_index < allowed_length / 2) { |
+ base = name.substr(0, ext_index); |
+ ext = name.substr(ext_index); |
+ } |
+ |
+ PathString name_base = GetConflictPathnameBase(base); |
+ if (name_base.length() + ext.length() > allowed_length) { |
+ name_base.resize(allowed_length - ext.length()); |
+ TrimPathStringToValidCharacter(&name_base); |
+ } |
+ PathString new_name = name_base + ext; |
+ int n = 2; |
+ while (Entry(trans, |
+ syncable::GET_BY_PARENTID_AND_DBNAME, |
+ parent_id, |
+ new_name).good()) { |
+ PathString local_ext = PSTR("("); |
+ local_ext.append(IntToPathString(n)); |
+ local_ext.append(PSTR(")")); |
+ local_ext.append(ext); |
+ if (name_base.length() + local_ext.length() > allowed_length) { |
+ name_base.resize(allowed_length - local_ext.length()); |
+ TrimPathStringToValidCharacter(&name_base); |
+ } |
+ new_name = name_base + local_ext; |
+ n++; |
+ } |
+ |
+ CHECK(new_name.length() <= kSyncProtocolMaxNameLengthBytes); |
+ return Name(new_name); |
+} |
+ |
+} // namespace |
+ |
+void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { |
+ // An update matches local actions, merge the changes. |
+ // This is a little fishy because we don't actually merge them. |
+ // In the future we should do a 3-way merge. |
+ LOG(INFO) << "Server and local changes match, merging:" << entry; |
+ // With IS_UNSYNCED false, changes should be merged. |
+ // METRIC simple conflict resolved by merge. |
+ entry->Put(syncable::IS_UNSYNCED, false); |
+} |
+ |
+void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, |
+ MutableEntry * entry) { |
idana
2009/09/10 05:44:37
nit: "MutableEntry * entry" -> "MutableEntry* entr
|
+ // This is similar to an overwrite from the old client. |
+ // This is equivalent to a scenario where we got the update before we'd |
+ // made our local client changes. |
+ // TODO(chron): This is really a general property clobber. We clobber |
+ // the server side property. Perhaps we should actually do property merging. |
+ entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); |
+ entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); |
+ // METRIC conflict resolved by overwrite. |
+} |
+ |
+ConflictResolver::ProcessSimpleConflictResult |
+ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
+ Id id, |
+ SyncerSession* session) { |
+ MutableEntry entry(trans, syncable::GET_BY_ID, id); |
+ // Must be good as the entry won't have been cleaned up. |
+ CHECK(entry.good()); |
+ // If an update fails, locally we have to be in a set or unsynced. We're not |
+ // in a set here, so we must be unsynced. |
+ if (!entry.Get(syncable::IS_UNSYNCED)) |
+ return NO_SYNC_PROGRESS; |
+ if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE)) { |
+ if (!entry.Get(syncable::PARENT_ID).ServerKnows()) { |
+ LOG(INFO) << "Item conflicting because its parent not yet committed. " |
+ "Id: "<< id; |
+ } else { |
+ LOG(INFO) << "No set for conflicting entry id " << id << ". There should " |
+ "be an update/commit that will fix this soon. This message should " |
+ "not repeat."; |
+ } |
+ return NO_SYNC_PROGRESS; |
+ } |
+ if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { |
+ // we've both deleted it, so lets just drop the need to commit/update this |
+ // entry. |
+ entry.Put(syncable::IS_UNSYNCED, false); |
+ entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
+ // we've made changes, but they won't help syncing progress. |
+ // METRIC simple conflict resolved by merge. |
+ return NO_SYNC_PROGRESS; |
+ } |
+ |
+ if (!entry.Get(syncable::SERVER_IS_DEL)) { |
+ // TODO(chron): Should we check more fields? Since IS_UNSYNCED is |
+ // turned on, this is really probably enough as fields will be overwritten. |
+ // Check if there's no changes. |
+ |
+ // Verbose but easier to debug. |
+ bool name_matches = entry.SyncNameMatchesServerName(); |
+ bool parent_matches = entry.Get(syncable::PARENT_ID) == |
+ entry.Get(syncable::SERVER_PARENT_ID); |
+ bool entry_deleted = entry.Get(syncable::IS_DEL); |
+ |
+ if (!entry_deleted && name_matches && parent_matches) { |
+ LOG(INFO) << "Resolving simple conflict, ignoring local changes for:" |
+ << entry; |
+ IgnoreLocalChanges(&entry); |
+ } else { |
+ LOG(INFO) << "Resolving simple conflict, overwriting server" |
+ " changes for:" << entry; |
+ OverwriteServerChanges(trans, &entry); |
+ } |
+ return SYNC_PROGRESS; |
+ } else { // SERVER_IS_DEL is true |
+ // If a server deleted folder has local contents we should be in a set. |
+ if (entry.Get(syncable::IS_DIR)) { |
+ Directory::ChildHandles children; |
+ trans->directory()->GetChildHandles(trans, |
+ entry.Get(syncable::ID), |
+ &children); |
+ if (0 != children.size()) { |
+ LOG(INFO) << "Entry is a server deleted directory with local contents, " |
+ "should be in a set. (race condition)."; |
+ return NO_SYNC_PROGRESS; |
+ } |
+ } |
+ // METRIC conflict resolved by entry split; |
+ |
+ // If the entry's deleted on the server, we can have a directory here. |
+ entry.Put(syncable::IS_UNSYNCED, true); |
+ |
+ SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); |
+ |
+ MutableEntry server_update(trans, syncable::GET_BY_ID, id); |
+ CHECK(server_update.good()); |
+ CHECK(server_update.Get(syncable::META_HANDLE) != |
+ entry.Get(syncable::META_HANDLE)) |
+ << server_update << entry; |
+ |
+ return SYNC_PROGRESS; |
+ } |
+} |
+ |
+namespace { |
+ |
+bool NamesCollideWithChildrenOfFolder(BaseTransaction* trans, |
+ const Directory::ChildHandles& children, |
+ Id folder_id) { |
+ Directory::ChildHandles::const_iterator i = children.begin(); |
+ while (i != children.end()) { |
+ Entry child(trans, syncable::GET_BY_HANDLE, *i); |
+ CHECK(child.good()); |
+ if (Entry(trans, |
+ syncable::GET_BY_PARENTID_AND_DBNAME, |
+ folder_id, |
+ child.GetName().db_value()).good()) |
+ return true; |
+ ++i; |
+ } |
+ return false; |
+} |
+ |
+void GiveEntryNewName(WriteTransaction* trans, |
+ MutableEntry* entry) { |
+ using namespace syncable; |
+ Name new_name = |
+ FindNewName(trans, entry->Get(syncable::PARENT_ID), entry->GetName()); |
+ LOG(INFO) << "Resolving name clash, renaming " << *entry << " to " |
+ << new_name.db_value(); |
+ entry->PutName(new_name); |
+ CHECK(entry->Get(syncable::IS_UNSYNCED)); |
+} |
+ |
+} // namespace |
+ |
+bool ConflictResolver::AttemptItemMerge(WriteTransaction* trans, |
+ MutableEntry* locally_named, |
+ MutableEntry* server_named) { |
+ // To avoid complications we only merge new entries with server entries. |
+ if (locally_named->Get(syncable::IS_DIR) != |
+ server_named->Get(syncable::SERVER_IS_DIR) || |
+ locally_named->Get(syncable::ID).ServerKnows() || |
+ locally_named->Get(syncable::IS_UNAPPLIED_UPDATE) || |
+ server_named->Get(syncable::IS_UNSYNCED)) |
+ return false; |
+ Id local_id = locally_named->Get(syncable::ID); |
+ Id desired_id = server_named->Get(syncable::ID); |
+ if (locally_named->Get(syncable::IS_DIR)) { |
+ // Extra work for directory name clash. We have to make sure we don't have |
+ // clashing child items, and update the parent id the children of the new |
+ // entry. |
+ Directory::ChildHandles children; |
+ trans->directory()->GetChildHandles(trans, local_id, &children); |
+ if (NamesCollideWithChildrenOfFolder(trans, children, desired_id)) |
+ return false; |
+ |
+ LOG(INFO) << "Merging local changes to: " << desired_id << ". " |
+ << *locally_named; |
+ |
+ server_named->Put(syncable::ID, trans->directory()->NextId()); |
+ Directory::ChildHandles::iterator i; |
+ for (i = children.begin() ; i != children.end() ; ++i) { |
+ MutableEntry child_entry(trans, syncable::GET_BY_HANDLE, *i); |
+ CHECK(child_entry.good()); |
+ CHECK(child_entry.Put(syncable::PARENT_ID, desired_id)); |
+ CHECK(child_entry.Put(syncable::IS_UNSYNCED, true)); |
+ Id id = child_entry.Get(syncable::ID); |
+ // we only note new entries for quicker merging next round. |
+ if (!id.ServerKnows()) |
+ children_of_merged_dirs_.insert(id); |
+ } |
+ } else { |
+ if (!server_named->Get(syncable::IS_DEL)) |
+ return false; |
+ } |
+ |
+ LOG(INFO) << "Identical client and server items merging server changes. " << |
+ *locally_named << " server: " << *server_named; |
+ |
+ // Clear server_named's server data and mark it deleted so it goes away |
+ // quietly because it's now identical to a deleted local entry. |
+ // locally_named takes on the ID of the server entry. |
+ server_named->Put(syncable::ID, trans->directory()->NextId()); |
+ locally_named->Put(syncable::ID, desired_id); |
+ locally_named->Put(syncable::IS_UNSYNCED, false); |
+ CopyServerFields(server_named, locally_named); |
+ ClearServerData(server_named); |
+ server_named->Put(syncable::IS_DEL, true); |
+ server_named->Put(syncable::BASE_VERSION, 0); |
+ CHECK(SUCCESS == |
+ SyncerUtil::AttemptToUpdateEntryWithoutMerge( |
+ trans, locally_named, NULL, NULL)); |
+ return true; |
+} |
+ |
+ConflictResolver::ServerClientNameClashReturn |
+ConflictResolver::ProcessServerClientNameClash(WriteTransaction* trans, |
+ MutableEntry* locally_named, |
+ MutableEntry* server_named, |
+ SyncerSession* session) { |
+ if (!locally_named->ExistsOnClientBecauseDatabaseNameIsNonEmpty()) |
+ return NO_CLASH; // locally_named is a server update. |
+ if (locally_named->Get(syncable::IS_DEL) || |
+ server_named->Get(syncable::SERVER_IS_DEL)) { |
+ return NO_CLASH; |
+ } |
+ if (locally_named->Get(syncable::PARENT_ID) != |
+ server_named->Get(syncable::SERVER_PARENT_ID)) { |
+ return NO_CLASH; // different parents |
+ } |
+ |
+ PathString name = locally_named->GetSyncNameValue(); |
+ if (0 != syncable::ComparePathNames(name, |
+ server_named->Get(syncable::SERVER_NAME))) { |
+ return NO_CLASH; // different names. |
+ } |
+ |
+ // First try to merge. |
+ if (AttemptItemMerge(trans, locally_named, server_named)) { |
+ // METRIC conflict resolved by merge |
+ return SOLVED; |
+ } |
+ // We need to rename. |
+ if (!locally_named->Get(syncable::IS_UNSYNCED)) { |
+ LOG(ERROR) << "Locally named part of a name conflict not unsynced?"; |
+ locally_named->Put(syncable::IS_UNSYNCED, true); |
+ } |
+ if (!server_named->Get(syncable::IS_UNAPPLIED_UPDATE)) { |
+ LOG(ERROR) << "Server named part of a name conflict not an update?"; |
+ } |
+ GiveEntryNewName(trans, locally_named); |
+ |
+ // METRIC conflict resolved by rename |
+ return SOLVED; |
+} |
+ |
+ConflictResolver::ServerClientNameClashReturn |
+ConflictResolver::ProcessNameClashesInSet(WriteTransaction* trans, |
+ ConflictSet* conflict_set, |
+ SyncerSession* session) { |
+ ConflictSet::const_iterator i,j; |
+ for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { |
+ MutableEntry entryi(trans, syncable::GET_BY_ID, *i); |
+ if (!entryi.Get(syncable::IS_UNSYNCED) && |
+ !entryi.Get(syncable::IS_UNAPPLIED_UPDATE)) |
+ // This set is broken / doesn't make sense, this may be transient. |
+ return BOGUS_SET; |
+ for (j = conflict_set->begin() ; *i != *j ; ++j) { |
+ MutableEntry entryj(trans, syncable::GET_BY_ID, *j); |
+ ServerClientNameClashReturn rv = |
+ ProcessServerClientNameClash(trans, &entryi, &entryj, session); |
+ if (NO_CLASH == rv) |
+ rv = ProcessServerClientNameClash(trans, &entryj, &entryi, session); |
+ if (NO_CLASH != rv) |
+ return rv; |
+ } |
+ } |
+ return NO_CLASH; |
+} |
+ |
+ConflictResolver::ConflictSetCountMapKey ConflictResolver::GetSetKey( |
+ ConflictSet* set) { |
+ // TODO(sync): Come up with a better scheme for set hashing. This scheme |
+ // will make debugging easy. |
+ // If this call to sort is removed, we need to add one before we use |
+ // binary_search in ProcessConflictSet |
+ sort(set->begin(), set->end()); |
+ std::stringstream rv; |
+ for(ConflictSet::iterator i = set->begin() ; i != set->end() ; ++i ) |
+ rv << *i << "."; |
+ return rv.str(); |
+} |
+ |
+namespace { |
+ |
+bool AttemptToFixCircularConflict(WriteTransaction* trans, |
+ ConflictSet* conflict_set) { |
+ ConflictSet::const_iterator i, j; |
+ for(i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { |
+ MutableEntry entryi(trans, syncable::GET_BY_ID, *i); |
+ if (entryi.Get(syncable::PARENT_ID) == |
+ entryi.Get(syncable::SERVER_PARENT_ID) || |
+ !entryi.Get(syncable::IS_UNAPPLIED_UPDATE) || |
+ !entryi.Get(syncable::IS_DIR)) { |
+ continue; |
+ } |
+ Id parentid = entryi.Get(syncable::SERVER_PARENT_ID); |
+ // Create the entry here as it's the only place we could ever get a parentid |
+ // that doesn't correspond to a real entry. |
+ Entry parent(trans, syncable::GET_BY_ID, parentid); |
+ if (!parent.good()) // server parent update not received yet |
+ continue; |
+ // This loop walks upwards from the server parent. If we hit the root (0) |
+ // all is well. If we hit the entry we're examining it means applying the |
+ // parent id would cause a loop. We don't need more general loop detection |
+ // because we know our local tree is valid. |
+ while (!parentid.IsRoot()) { |
+ Entry parent(trans, syncable::GET_BY_ID, parentid); |
+ CHECK(parent.good()); |
+ if (parentid == *i) |
+ break; // it's a loop |
+ parentid = parent.Get(syncable::PARENT_ID); |
+ } |
+ if (parentid.IsRoot()) |
+ continue; |
+ LOG(INFO) << "Overwriting server changes to avoid loop: " << entryi; |
+ entryi.Put(syncable::BASE_VERSION, entryi.Get(syncable::SERVER_VERSION)); |
+ entryi.Put(syncable::IS_UNSYNCED, true); |
+ entryi.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
+ // METRIC conflict resolved by breaking dir loop. |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool AttemptToFixUnsyncedEntryInDeletedServerTree(WriteTransaction* trans, |
+ ConflictSet* conflict_set, |
+ const Entry& entry) { |
+ if (!entry.Get(syncable::IS_UNSYNCED) || entry.Get(syncable::IS_DEL)) |
+ return false; |
+ Id parentid = entry.Get(syncable::PARENT_ID); |
+ MutableEntry parent(trans, syncable::GET_BY_ID, parentid); |
+ if (!parent.good() || !parent.Get(syncable::IS_UNAPPLIED_UPDATE) || |
+ !parent.Get(syncable::SERVER_IS_DEL) || |
+ !binary_search(conflict_set->begin(), conflict_set->end(), parentid)) |
+ return false; |
+ // We're trying to commit into a directory tree that's been deleted. |
+ // To solve this we recreate the directory tree. |
+ // |
+ // We do this in two parts, first we ensure the tree is unaltered since the |
+ // conflict was detected. |
+ Id id = parentid; |
+ while (!id.IsRoot()) { |
+ if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) |
+ break; |
+ Entry parent(trans, syncable::GET_BY_ID, id); |
+ if (!parent.good() || !parent.Get(syncable::IS_UNAPPLIED_UPDATE) || |
+ !parent.Get(syncable::SERVER_IS_DEL)) |
+ return false; |
+ id = parent.Get(syncable::PARENT_ID); |
+ } |
+ // Now we fix up the entries. |
+ id = parentid; |
+ while (!id.IsRoot()) { |
+ MutableEntry parent(trans, syncable::GET_BY_ID, id); |
+ if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) |
+ break; |
+ LOG(INFO) << "Giving directory a new id so we can undelete it " |
+ << parent; |
+ ClearServerData(&parent); |
+ SyncerUtil::ChangeEntryIDAndUpdateChildren(trans, &parent, |
+ trans->directory()->NextId()); |
+ parent.Put(syncable::BASE_VERSION, 0); |
+ parent.Put(syncable::IS_UNSYNCED, true); |
+ id = parent.Get(syncable::PARENT_ID); |
+ // METRIC conflict resolved by recreating dir tree. |
+ } |
+ return true; |
+} |
+ |
+bool AttemptToFixUpdateEntryInDeletedLocalTree(WriteTransaction* trans, |
+ ConflictSet* conflict_set, |
+ const Entry& entry) { |
+ if (!entry.Get(syncable::IS_UNAPPLIED_UPDATE) || |
+ entry.Get(syncable::SERVER_IS_DEL)) |
+ return false; |
+ Id parent_id = entry.Get(syncable::SERVER_PARENT_ID); |
+ MutableEntry parent(trans, syncable::GET_BY_ID, parent_id); |
+ if (!parent.good() || !parent.Get(syncable::IS_DEL) || |
+ !binary_search(conflict_set->begin(), conflict_set->end(), parent_id)) { |
+ return false; |
+ } |
+ // We've deleted a directory tree that's got contents on the server. |
+ // We recreate the directory to solve the problem. |
+ // |
+ // We do this in two parts, first we ensure the tree is unaltered since |
+ // the conflict was detected. |
+ Id id = parent_id; |
+ // As we will be crawling the path of deleted entries there's a chance |
+ // we'll end up having to reparent an item as there will be an invalid |
+ // parent. |
+ Id reroot_id = syncable::kNullId; |
+ // similarly crawling deleted items means we risk loops. |
+ int loop_detection = conflict_set->size(); |
+ while (!id.IsRoot() && --loop_detection >= 0) { |
+ Entry parent(trans, syncable::GET_BY_ID, id); |
+ // If we get a bad parent, or a parent that's deleted on client and |
+ // server we recreate the hierarchy in the root. |
+ if (!parent.good()) { |
+ reroot_id = id; |
+ break; |
+ } |
+ CHECK(parent.Get(syncable::IS_DIR)); |
+ if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) { |
+ // We've got to an entry that's not in the set. If it has been |
+ // deleted between set building and this point in time we |
+ // return false. If it had been deleted earlier it would have been |
+ // in the set. |
+ // TODO(sync): Revisit syncer code organization to see if |
+ // conflict resolution can be done in the same transaction as set |
+ // building. |
+ if (parent.Get(syncable::IS_DEL)) |
+ return false; |
+ break; |
+ } |
+ if (!parent.Get(syncable::IS_DEL) || |
+ parent.Get(syncable::SERVER_IS_DEL) || |
+ !parent.Get(syncable::IS_UNSYNCED)) { |
+ return false; |
+ } |
+ id = parent.Get(syncable::PARENT_ID); |
+ } |
+ // If we find we've been looping we re-root the hierarchy. |
+ if (loop_detection < 0) |
+ if (id == entry.Get(syncable::ID)) |
+ reroot_id = entry.Get(syncable::PARENT_ID); |
+ else |
+ reroot_id = id; |
+ // Now we fix things up by undeleting all the folders in the item's |
+ // path. |
+ id = parent_id; |
+ while (!id.IsRoot() && id != reroot_id) { |
+ if (!binary_search(conflict_set->begin(), conflict_set->end(), id)) |
+ break; |
+ MutableEntry entry(trans, syncable::GET_BY_ID, id); |
+ Id parent_id = entry.Get(syncable::PARENT_ID); |
+ if (parent_id == reroot_id) |
+ parent_id = trans->root_id(); |
+ Name old_name = entry.GetName(); |
+ Name new_name = FindNewName(trans, parent_id, old_name); |
+ LOG(INFO) << "Undoing our deletion of " << entry << |
+ ", will have name " << new_name.db_value(); |
+ if (new_name != old_name || parent_id != entry.Get(syncable::PARENT_ID)) |
+ CHECK(entry.PutParentIdAndName(parent_id, new_name)); |
+ entry.Put(syncable::IS_DEL, false); |
+ id = entry.Get(syncable::PARENT_ID); |
+ // METRIC conflict resolved by recreating dir tree. |
+ } |
+ return true; |
+} |
+ |
+bool AttemptToFixRemovedDirectoriesWithContent(WriteTransaction* trans, |
+ ConflictSet* conflict_set) { |
+ ConflictSet::const_iterator i,j; |
+ for (i = conflict_set->begin() ; i != conflict_set->end() ; ++i) { |
+ Entry entry(trans, syncable::GET_BY_ID, *i); |
+ if (AttemptToFixUnsyncedEntryInDeletedServerTree(trans, |
+ conflict_set, entry)) { |
+ return true; |
+ } |
+ if (AttemptToFixUpdateEntryInDeletedLocalTree(trans, conflict_set, entry)) |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+} // namespace |
+ |
+bool ConflictResolver::ProcessConflictSet(WriteTransaction* trans, |
+ ConflictSet* conflict_set, |
+ int conflict_count, |
+ SyncerSession* session) { |
+ int set_size = conflict_set->size(); |
+ if (set_size < 2) { |
+ LOG(WARNING) << "Skipping conflict set because it has size " << set_size; |
+ // We can end up with sets of size one if we have a new item in a set that |
+ // we tried to commit transactionally. This should not be a persistent |
+ // situation. |
+ return false; |
+ } |
+ if (conflict_count < 3) { |
+ // Avoid resolving sets that could be the result of transient conflicts. |
+ // Transient conflicts can occur because the client or server can be |
+ // slightly out of date. |
+ return false; |
+ } |
+ |
+ LOG(INFO) << "Fixing a set containing " << set_size << " items"; |
+ |
+ ServerClientNameClashReturn rv = ProcessNameClashesInSet(trans, conflict_set, |
+ session); |
+ if (SOLVED == rv) |
+ return true; |
+ if (NO_CLASH != rv) |
+ return false; |
+ |
+ // Fix circular conflicts. |
+ if (AttemptToFixCircularConflict(trans, conflict_set)) |
+ return true; |
+ // Check for problems involving contents of removed folders. |
+ if (AttemptToFixRemovedDirectoriesWithContent(trans, conflict_set)) |
+ return true; |
+ return false; |
+} |
+ |
+ |
idana
2009/09/10 05:44:37
Please remove extra blank line.
|
+template <typename InputIt> |
+bool ConflictResolver::LogAndSignalIfConflictStuck( |
+ BaseTransaction* trans, |
+ int attempt_count, |
+ InputIt begin, |
+ InputIt end, |
+ ConflictResolutionView* view) { |
+ if (attempt_count < SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT) |
+ return false; |
+ |
+ // Don't signal stuck if we're not up to date. |
+ if (view->servers_latest_timestamp() != view->current_sync_timestamp()) |
+ return false; |
+ |
+ LOG(ERROR) << "[BUG] Conflict set cannot be resolved, has " |
+ << end - begin << " items:"; |
+ for (InputIt i = begin ; i != end ; ++i) { |
+ Entry e(trans, syncable::GET_BY_ID, *i); |
+ if (e.good()) |
+ LOG(ERROR) << " " << e; |
+ else |
+ LOG(ERROR) << " Bad ID:" << *i; |
+ } |
+ |
+ view->set_syncer_stuck(true); |
+ |
+ return true; |
+ // TODO(sync): If we're stuck for a while we need to alert the user, |
+ // clear cache or reset syncing. At the very least we should stop trying |
+ // something that's obviously not working. |
+} |
+ |
+bool ConflictResolver::ResolveSimpleConflicts(const ScopedDirLookup& dir, |
+ ConflictResolutionView* view, |
+ SyncerSession *session) { |
idana
2009/09/10 05:44:37
"SyncerSession *session" -> "SyncerSession* sessio
|
+ WriteTransaction trans(dir, syncable::SYNCER, __FILE__, __LINE__); |
+ bool forward_progress = false; |
+ // First iterate over simple conflict items (those that belong to no set). |
+ set<Id>::const_iterator conflicting_item_it; |
+ for (conflicting_item_it = view->CommitConflictsBegin(); |
+ conflicting_item_it != view->CommitConflictsEnd() ; |
+ ++conflicting_item_it) { |
+ Id id = *conflicting_item_it; |
+ map<Id, ConflictSet*>::const_iterator item_set_it = |
+ view->IdToConflictSetFind(id); |
+ if (item_set_it == view->IdToConflictSetEnd() || |
+ 0 == item_set_it->second) { |
+ // We have a simple conflict. |
+ switch(ProcessSimpleConflict(&trans, id, session)) { |
+ case NO_SYNC_PROGRESS: |
+ { |
+ int conflict_count = (simple_conflict_count_map_[id] += 2); |
+ bool stuck = LogAndSignalIfConflictStuck(&trans, conflict_count, |
+ &id, &id + 1, view); |
+ break; |
+ } |
+ case SYNC_PROGRESS: |
+ forward_progress = true; |
+ break; |
+ } |
+ } |
+ } |
+ // Reduce the simple_conflict_count for each item currently tracked. |
+ SimpleConflictCountMap::iterator i = simple_conflict_count_map_.begin(); |
+ while (i != simple_conflict_count_map_.end()) { |
+ if (0 == --(i->second)) |
+ simple_conflict_count_map_.erase(i++); |
+ else |
+ ++i; |
+ } |
+ return forward_progress; |
+} |
+ |
+bool ConflictResolver::ResolveConflicts(const ScopedDirLookup& dir, |
+ ConflictResolutionView* view, |
+ SyncerSession *session) { |
idana
2009/09/10 05:44:37
"SyncerSession *session" -> "SyncerSession* sessio
|
+ if (view->HasBlockedItems()) { |
+ LOG(INFO) << "Delaying conflict resolution, have " << |
+ view->BlockedItemsSize() << " blocked items."; |
+ return false; |
+ } |
+ bool rv = false; |
+ if (ResolveSimpleConflicts(dir, view, session)) |
+ rv = true; |
+ WriteTransaction trans(dir, syncable::SYNCER, __FILE__, __LINE__); |
+ set<Id> children_of_dirs_merged_last_round; |
+ std::swap(children_of_merged_dirs_, children_of_dirs_merged_last_round); |
+ set<ConflictSet*>::const_iterator set_it; |
+ for (set_it = view->ConflictSetsBegin(); |
+ set_it != view->ConflictSetsEnd(); |
+ set_it++) { |
+ ConflictSet* conflict_set = *set_it; |
+ ConflictSetCountMapKey key = GetSetKey(conflict_set); |
+ conflict_set_count_map_[key] += 2; |
+ int conflict_count = conflict_set_count_map_[key]; |
+ // Keep a metric for new sets. |
+ if (2 == conflict_count) { |
+ // METRIC conflict sets seen ++ |
+ } |
+ // See if this set contains entries whose parents were merged last round. |
+ if (SortedCollectionsIntersect(children_of_dirs_merged_last_round.begin(), |
+ children_of_dirs_merged_last_round.end(), |
+ conflict_set->begin(), |
+ conflict_set->end())) { |
+ LOG(INFO) << "Accelerating resolution for hierarchical merge."; |
+ conflict_count += 2; |
+ } |
+ // See if we should process this set. |
+ if (ProcessConflictSet(&trans, conflict_set, conflict_count, session)) { |
+ rv = true; |
+ } |
+ SyncerStatus status(session); |
+ bool stuck = LogAndSignalIfConflictStuck(&trans, conflict_count, |
+ conflict_set->begin(), |
+ conflict_set->end(), view); |
+ } |
+ if (rv) { |
+ // This code means we don't signal that syncing is stuck when any conflict |
+ // resolution has occured. |
+ // TODO(sync): As this will also reduce our sensitivity to problem |
+ // conditions and increase the time for cascading resolutions we may have to |
+ // revisit this code later, doing something more intelligent. |
+ conflict_set_count_map_.clear(); |
+ simple_conflict_count_map_.clear(); |
+ } |
+ ConflictSetCountMap::iterator i = conflict_set_count_map_.begin(); |
+ while (i != conflict_set_count_map_.end()) { |
+ if (0 == --i->second) { |
+ conflict_set_count_map_.erase(i++); |
+ // METRIC self resolved conflict sets ++. |
+ } else { |
+ ++i; |
+ } |
+ } |
+ return rv; |
+} |
+ |
+} // namespace browser_sync |
Property changes on: chrome\browser\sync\engine\conflict_resolver.cc |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |