Chromium Code Reviews
|
| 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 |