OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/sync/engine/conflict_resolver.h" | 5 #include "chrome/browser/sync/engine/conflict_resolver.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <list> |
8 #include <map> | 9 #include <map> |
9 #include <set> | 10 #include <set> |
10 | 11 |
11 #include "base/location.h" | 12 #include "base/location.h" |
12 #include "base/metrics/histogram.h" | 13 #include "base/metrics/histogram.h" |
13 #include "chrome/browser/sync/engine/syncer.h" | 14 #include "chrome/browser/sync/engine/syncer.h" |
14 #include "chrome/browser/sync/engine/syncer_util.h" | 15 #include "chrome/browser/sync/engine/syncer_util.h" |
15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" | 16 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" |
16 #include "chrome/browser/sync/protocol/service_constants.h" | 17 #include "chrome/browser/sync/protocol/service_constants.h" |
17 #include "chrome/browser/sync/sessions/status_controller.h" | 18 #include "chrome/browser/sync/sessions/status_controller.h" |
18 #include "chrome/browser/sync/syncable/directory_manager.h" | 19 #include "chrome/browser/sync/syncable/directory_manager.h" |
19 #include "chrome/browser/sync/syncable/syncable.h" | 20 #include "chrome/browser/sync/syncable/syncable.h" |
20 #include "chrome/browser/sync/util/cryptographer.h" | 21 #include "chrome/browser/sync/util/cryptographer.h" |
21 | 22 |
| 23 using std::list; |
22 using std::map; | 24 using std::map; |
23 using std::set; | 25 using std::set; |
24 using syncable::BaseTransaction; | 26 using syncable::BaseTransaction; |
25 using syncable::Directory; | 27 using syncable::Directory; |
26 using syncable::Entry; | 28 using syncable::Entry; |
27 using syncable::GetModelTypeFromSpecifics; | 29 using syncable::GetModelTypeFromSpecifics; |
28 using syncable::Id; | 30 using syncable::Id; |
29 using syncable::IsRealDataType; | 31 using syncable::IsRealDataType; |
30 using syncable::MutableEntry; | 32 using syncable::MutableEntry; |
31 using syncable::ScopedDirLookup; | 33 using syncable::ScopedDirLookup; |
(...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
103 return NO_SYNC_PROGRESS; | 105 return NO_SYNC_PROGRESS; |
104 } | 106 } |
105 | 107 |
106 // This logic determines "client wins" vs. "server wins" strategy picking. | 108 // This logic determines "client wins" vs. "server wins" strategy picking. |
107 // By the time we get to this point, we rely on the following to be true: | 109 // By the time we get to this point, we rely on the following to be true: |
108 // a) We can decrypt both the local and server data (else we'd be in | 110 // a) We can decrypt both the local and server data (else we'd be in |
109 // conflict encryption and not attempting to resolve). | 111 // conflict encryption and not attempting to resolve). |
110 // b) All unsynced changes have been re-encrypted with the default key ( | 112 // b) All unsynced changes have been re-encrypted with the default key ( |
111 // occurs either in AttemptToUpdateEntry, SetPassphrase, or | 113 // occurs either in AttemptToUpdateEntry, SetPassphrase, or |
112 // RefreshEncryption). | 114 // RefreshEncryption). |
113 // c) Prev_server_specifics having a valid datatype means that we received | 115 // c) Base_server_specifics having a valid datatype means that we received |
114 // an undecryptable update that only changed specifics, and since then have | 116 // an undecryptable update that only changed specifics, and since then have |
115 // not received any further non-specifics-only or decryptable updates. | 117 // not received any further non-specifics-only or decryptable updates. |
116 // d) If the server_specifics match specifics, server_specifics are | 118 // d) If the server_specifics match specifics, server_specifics are |
117 // encrypted with the default key, and all other visible properties match, | 119 // encrypted with the default key, and all other visible properties match, |
118 // then we can safely ignore the local changes as redundant. | 120 // then we can safely ignore the local changes as redundant. |
119 // e) Otherwise if the base_server_specifics match the server_specifics, no | 121 // e) Otherwise if the base_server_specifics match the server_specifics, no |
120 // functional change must have been made server-side (else | 122 // functional change must have been made server-side (else |
121 // base_server_specifics would have been cleared), and we can therefore | 123 // base_server_specifics would have been cleared), and we can therefore |
122 // safely ignore the server changes as redundant. | 124 // safely ignore the server changes as redundant. |
123 // f) Otherwise, it's in general safer to ignore local changes, with the | 125 // f) Otherwise, it's in general safer to ignore local changes, with the |
124 // exception of deletion conflicts (choose to undelete) and conflicts | 126 // exception of deletion conflicts (choose to undelete) and conflicts |
125 // where the non_unique_name or parent don't match. | 127 // where the non_unique_name or parent don't match. |
126 // TODO(sync): We can't compare position here without performing the | |
127 // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it | |
128 // into account. | |
129 if (!entry.Get(syncable::SERVER_IS_DEL)) { | 128 if (!entry.Get(syncable::SERVER_IS_DEL)) { |
130 // TODO(nick): The current logic is arbitrary; instead, it ought to be made | 129 // TODO(nick): The current logic is arbitrary; instead, it ought to be made |
131 // consistent with the ModelAssociator behavior for a datatype. It would | 130 // consistent with the ModelAssociator behavior for a datatype. It would |
132 // be nice if we could route this back to ModelAssociator code to pick one | 131 // be nice if we could route this back to ModelAssociator code to pick one |
133 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) | 132 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) |
134 // are easily mergeable. | 133 // are easily mergeable. |
135 // See http://crbug.com/77339. | 134 // See http://crbug.com/77339. |
136 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == | 135 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
137 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); | 136 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
138 bool parent_matches = entry.Get(syncable::PARENT_ID) == | 137 bool parent_matches = entry.Get(syncable::PARENT_ID) == |
139 entry.Get(syncable::SERVER_PARENT_ID); | 138 entry.Get(syncable::SERVER_PARENT_ID); |
140 bool entry_deleted = entry.Get(syncable::IS_DEL); | 139 bool entry_deleted = entry.Get(syncable::IS_DEL); |
| 140 |
| 141 // This positional check is meant to be necessary but not sufficient. As a |
| 142 // result, it may be false even when the position hasn't changed, possibly |
| 143 // resulting in unnecessary commits, but if it's true the position has |
| 144 // definitely not changed. The check works by verifying that the prev id |
| 145 // as calculated from the server position (which will ignore any |
| 146 // unsynced/unapplied predecessors and be root for non-bookmark datatypes) |
| 147 // matches the client prev id. Because we traverse chains of conflicting |
| 148 // items in predecessor -> successor order, we don't need to also verify the |
| 149 // successor matches (If it's in conflict, we'll verify it next. If it's |
| 150 // not, then it should be taken into account already in the |
| 151 // ComputePrevIdFromServerPosition calculation). This works even when there |
| 152 // are chains of conflicting items. |
| 153 // |
| 154 // Example: Original sequence was abcde. Server changes to aCDbe, while |
| 155 // client changes to aDCbe (C and D are in conflict). Locally, D's prev id |
| 156 // is a, while C's prev id is D. On the other hand, the server prev id will |
| 157 // ignore unsynced/unapplied items, so D's server prev id will also be a, |
| 158 // just like C's. Because we traverse in client predecessor->successor |
| 159 // order, we evaluate D first. Since prev id and server id match, we |
| 160 // consider the position to have remained the same for D, and will unset |
| 161 // it's UNSYNCED/UNAPPLIED bits. When we evaluate C though, we'll see that |
| 162 // the prev id is D locally while the server's prev id is a. C will |
| 163 // therefore count as a positional conflict (and the local data will be |
| 164 // overwritten by the server data typically). The final result will be |
| 165 // aCDbe (the same as the server's view). Even though both C and D were |
| 166 // modified, only one counted as being in actual conflict and was resolved |
| 167 // with local/server wins. |
| 168 // |
| 169 // In general, when there are chains of positional conflicts, only the first |
| 170 // item in chain (based on the clients point of view) will have both it's |
| 171 // server prev id and local prev id match. For all the rest the server prev |
| 172 // id will be the predecessor of the first item in the chain, and therefore |
| 173 // not match the local prev id. |
| 174 // |
| 175 // Similarly, chains of conflicts where the server and client info are the |
| 176 // same are supported due to the predecessor->successor ordering. In this |
| 177 // case, from the first item onward, we unset the UNSYNCED/UNAPPLIED bits as |
| 178 // we decide that nothing changed. The subsequent item's server prev id will |
| 179 // accurately match the local prev id because the predecessor is no longer |
| 180 // UNSYNCED/UNAPPLIED. |
| 181 // TODO(zea): simplify all this once we can directly compare server position |
| 182 // to client position. |
| 183 syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition( |
| 184 entry.Get(syncable::SERVER_PARENT_ID)); |
| 185 bool position_matches = parent_matches && |
| 186 server_prev_id == entry.Get(syncable::PREV_ID); |
| 187 DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id " |
| 188 << " is " << server_prev_id << ", local prev id is " |
| 189 << entry.Get(syncable::PREV_ID); |
141 const sync_pb::EntitySpecifics& specifics = | 190 const sync_pb::EntitySpecifics& specifics = |
142 entry.Get(syncable::SPECIFICS); | 191 entry.Get(syncable::SPECIFICS); |
143 const sync_pb::EntitySpecifics& server_specifics = | 192 const sync_pb::EntitySpecifics& server_specifics = |
144 entry.Get(syncable::SERVER_SPECIFICS); | 193 entry.Get(syncable::SERVER_SPECIFICS); |
145 const sync_pb::EntitySpecifics& base_server_specifics = | 194 const sync_pb::EntitySpecifics& base_server_specifics = |
146 entry.Get(syncable::BASE_SERVER_SPECIFICS); | 195 entry.Get(syncable::BASE_SERVER_SPECIFICS); |
147 std::string decrypted_specifics, decrypted_server_specifics; | 196 std::string decrypted_specifics, decrypted_server_specifics; |
148 bool specifics_match = false; | 197 bool specifics_match = false; |
149 bool server_encrypted_with_default_key = false; | 198 bool server_encrypted_with_default_key = false; |
150 if (specifics.has_encrypted()) { | 199 if (specifics.has_encrypted()) { |
(...skipping 60 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
211 // We deliberately leave the server's device information. This client will | 260 // We deliberately leave the server's device information. This client will |
212 // add it's own device information on restart. | 261 // add it's own device information on restart. |
213 entry.Put(syncable::SPECIFICS, specifics); | 262 entry.Put(syncable::SPECIFICS, specifics); |
214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; | 263 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; |
215 status->increment_num_server_overwrites(); | 264 status->increment_num_server_overwrites(); |
216 OverwriteServerChanges(trans, &entry); | 265 OverwriteServerChanges(trans, &entry); |
217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 266 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
218 NIGORI_MERGE, | 267 NIGORI_MERGE, |
219 CONFLICT_RESOLUTION_SIZE); | 268 CONFLICT_RESOLUTION_SIZE); |
220 } else if (!entry_deleted && name_matches && parent_matches && | 269 } else if (!entry_deleted && name_matches && parent_matches && |
221 specifics_match) { | 270 position_matches && specifics_match) { |
222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " | 271 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
223 << "changes for: " << entry; | 272 << "changes for: " << entry; |
224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the | 273 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the |
225 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset | 274 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset |
226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data | 275 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from |
227 // when the server update gets applied and the item is re-inserted into | 276 // adjacent entries when the server update gets applied and the item is |
228 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing | 277 // re-inserted into the PREV_ID/NEXT_ID linked list. |
229 // positional info). | |
230 OverwriteServerChanges(trans, &entry); | 278 OverwriteServerChanges(trans, &entry); |
231 IgnoreLocalChanges(&entry); | 279 IgnoreLocalChanges(&entry); |
232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 280 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
233 CHANGES_MATCH, | 281 CHANGES_MATCH, |
234 CONFLICT_RESOLUTION_SIZE); | 282 CONFLICT_RESOLUTION_SIZE); |
235 } else if (base_server_specifics_match) { | 283 } else if (base_server_specifics_match) { |
236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " | 284 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " |
237 << " changes for: " << entry; | 285 << " changes for: " << entry; |
238 status->increment_num_server_overwrites(); | 286 status->increment_num_server_overwrites(); |
239 OverwriteServerChanges(trans, &entry); | 287 OverwriteServerChanges(trans, &entry); |
240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 288 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
241 IGNORE_ENCRYPTION, | 289 IGNORE_ENCRYPTION, |
242 CONFLICT_RESOLUTION_SIZE); | 290 CONFLICT_RESOLUTION_SIZE); |
243 // Now that we've resolved the conflict, clear the prev server | |
244 // specifics. | |
245 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); | |
246 } else if (entry_deleted || !name_matches || !parent_matches) { | 291 } else if (entry_deleted || !name_matches || !parent_matches) { |
247 OverwriteServerChanges(trans, &entry); | 292 OverwriteServerChanges(trans, &entry); |
248 status->increment_num_server_overwrites(); | 293 status->increment_num_server_overwrites(); |
249 DVLOG(1) << "Resolving simple conflict, overwriting server changes " | 294 DVLOG(1) << "Resolving simple conflict, overwriting server changes " |
250 << "for: " << entry; | 295 << "for: " << entry; |
251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 296 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
252 OVERWRITE_SERVER, | 297 OVERWRITE_SERVER, |
253 CONFLICT_RESOLUTION_SIZE); | 298 CONFLICT_RESOLUTION_SIZE); |
254 } else { | 299 } else { |
255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " | 300 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " |
256 << entry; | 301 << entry; |
257 IgnoreLocalChanges(&entry); | 302 IgnoreLocalChanges(&entry); |
258 status->increment_num_local_overwrites(); | 303 status->increment_num_local_overwrites(); |
259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 304 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
260 OVERWRITE_LOCAL, | 305 OVERWRITE_LOCAL, |
261 CONFLICT_RESOLUTION_SIZE); | 306 CONFLICT_RESOLUTION_SIZE); |
262 } | 307 } |
| 308 // Now that we've resolved the conflict, clear the prev server |
| 309 // specifics. |
| 310 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); |
263 return SYNC_PROGRESS; | 311 return SYNC_PROGRESS; |
264 } else { // SERVER_IS_DEL is true | 312 } else { // SERVER_IS_DEL is true |
265 // If a server deleted folder has local contents we should be in a set. | 313 // If a server deleted folder has local contents we should be in a set. |
266 if (entry.Get(syncable::IS_DIR)) { | 314 if (entry.Get(syncable::IS_DIR)) { |
267 Directory::ChildHandles children; | 315 Directory::ChildHandles children; |
268 trans->directory()->GetChildHandlesById(trans, | 316 trans->directory()->GetChildHandlesById(trans, |
269 entry.Get(syncable::ID), | 317 entry.Get(syncable::ID), |
270 &children); | 318 &children); |
271 if (0 != children.size()) { | 319 if (0 != children.size()) { |
272 DVLOG(1) << "Entry is a server deleted directory with local contents, " | 320 DVLOG(1) << "Entry is a server deleted directory with local contents, " |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
311 } | 359 } |
312 | 360 |
313 bool ConflictResolver::ResolveSimpleConflicts( | 361 bool ConflictResolver::ResolveSimpleConflicts( |
314 syncable::WriteTransaction* trans, | 362 syncable::WriteTransaction* trans, |
315 const Cryptographer* cryptographer, | 363 const Cryptographer* cryptographer, |
316 const ConflictProgress& progress, | 364 const ConflictProgress& progress, |
317 sessions::StatusController* status) { | 365 sessions::StatusController* status) { |
318 bool forward_progress = false; | 366 bool forward_progress = false; |
319 // First iterate over simple conflict items (those that belong to no set). | 367 // First iterate over simple conflict items (those that belong to no set). |
320 set<Id>::const_iterator conflicting_item_it; | 368 set<Id>::const_iterator conflicting_item_it; |
| 369 set<Id> processed_items; |
321 for (conflicting_item_it = progress.ConflictingItemsBegin(); | 370 for (conflicting_item_it = progress.ConflictingItemsBegin(); |
322 conflicting_item_it != progress.ConflictingItemsEnd(); | 371 conflicting_item_it != progress.ConflictingItemsEnd(); |
323 ++conflicting_item_it) { | 372 ++conflicting_item_it) { |
324 Id id = *conflicting_item_it; | 373 Id id = *conflicting_item_it; |
| 374 if (processed_items.count(id) > 0) |
| 375 continue; |
325 map<Id, ConflictSet*>::const_iterator item_set_it = | 376 map<Id, ConflictSet*>::const_iterator item_set_it = |
326 progress.IdToConflictSetFind(id); | 377 progress.IdToConflictSetFind(id); |
327 if (item_set_it == progress.IdToConflictSetEnd() || | 378 if (item_set_it == progress.IdToConflictSetEnd() || |
328 0 == item_set_it->second) { | 379 0 == item_set_it->second) { |
329 // We have a simple conflict. | 380 // We have a simple conflict. In order check if positions have changed, |
330 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { | 381 // we need to process conflicting predecessors before successors. Traverse |
331 case NO_SYNC_PROGRESS: | 382 // backwards through all continuous conflicting predecessors, building a |
| 383 // stack of items to resolve in predecessor->successor order, then process |
| 384 // each item individually. |
| 385 list<Id> predecessors; |
| 386 Id prev_id = id; |
| 387 do { |
| 388 predecessors.push_back(prev_id); |
| 389 Entry entry(trans, syncable::GET_BY_ID, prev_id); |
| 390 // Any entry in conflict must be valid. |
| 391 CHECK(entry.good()); |
| 392 Id new_prev_id = entry.Get(syncable::PREV_ID); |
| 393 if (new_prev_id == prev_id) |
332 break; | 394 break; |
333 case SYNC_PROGRESS: | 395 prev_id = new_prev_id; |
334 forward_progress = true; | 396 } while (processed_items.count(prev_id) == 0 && |
335 break; | 397 progress.HasSimpleConflictItem(prev_id)); // Excludes root. |
| 398 while (!predecessors.empty()) { |
| 399 id = predecessors.back(); |
| 400 predecessors.pop_back(); |
| 401 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { |
| 402 case NO_SYNC_PROGRESS: |
| 403 break; |
| 404 case SYNC_PROGRESS: |
| 405 forward_progress = true; |
| 406 break; |
| 407 } |
| 408 processed_items.insert(id); |
336 } | 409 } |
337 } | 410 } |
338 } | 411 } |
339 return forward_progress; | 412 return forward_progress; |
340 } | 413 } |
341 | 414 |
342 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, | 415 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, |
343 const Cryptographer* cryptographer, | 416 const Cryptographer* cryptographer, |
344 const ConflictProgress& progress, | 417 const ConflictProgress& progress, |
345 sessions::StatusController* status) { | 418 sessions::StatusController* status) { |
346 // TODO(rlarocque): A good amount of code related to the resolution of | 419 // TODO(rlarocque): A good amount of code related to the resolution of |
347 // conflict sets has been deleted here. This was done because the code had | 420 // conflict sets has been deleted here. This was done because the code had |
348 // not been used in years. An unrelated bug fix accidentally re-enabled the | 421 // not been used in years. An unrelated bug fix accidentally re-enabled the |
349 // code, forcing us to make a decision about what we should do with the code. | 422 // code, forcing us to make a decision about what we should do with the code. |
350 // We decided to do the safe thing and delete it for now. This restores the | 423 // We decided to do the safe thing and delete it for now. This restores the |
351 // behaviour we've relied on for quite some time. We should think about what | 424 // behaviour we've relied on for quite some time. We should think about what |
352 // that code was trying to do and consider re-enabling parts of it. | 425 // that code was trying to do and consider re-enabling parts of it. |
353 | 426 |
354 if (progress.ConflictSetsSize() > 0) { | 427 if (progress.ConflictSetsSize() > 0) { |
355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() | 428 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() |
356 << " non-simple conflicting entries in " << progress.ConflictSetsSize() | 429 << " non-simple conflicting entries in " << progress.ConflictSetsSize() |
357 << " unprocessed conflict sets."; | 430 << " unprocessed conflict sets."; |
358 } | 431 } |
359 | 432 |
360 return ResolveSimpleConflicts(trans, cryptographer, progress, status); | 433 return ResolveSimpleConflicts(trans, cryptographer, progress, status); |
361 } | 434 } |
362 | 435 |
363 } // namespace browser_sync | 436 } // namespace browser_sync |
OLD | NEW |