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 // This positional check is conservative. It will likely be false if the | |
141 // predecessor is unsynced/unapplied. For this reason, we ensure conflicting | |
142 // predecessors are resolved before their successors. Being | |
143 // conservative results in being less likely to lose local positioning | |
144 // changes, with the cost of occasional unnecessary commits. | |
145 // TODO(zea): change this once we can directly compare server position to | |
146 // client position. | |
147 syncable::Id server_prev_id = entry.ComputePrevIdFromServerPosition( | |
rlarocque
2012/01/10 23:39:51
ComputePrevIdFromServerPosition() will not return
Nicolas Zea
2012/01/18 02:54:39
See my updated comments. Basically, this situation
rlarocque
2012/01/18 20:21:55
It sounds like you're expecting this to return the
Nicolas Zea
2012/01/18 20:40:42
No, I expect it to return the first fully synced i
| |
148 entry.Get(syncable::SERVER_PARENT_ID)); | |
rlarocque
2012/01/10 23:39:51
SERVER_PARENT_ID? Did you mean SERVER_PREV_ID?
Nicolas Zea
2012/01/18 02:54:39
No, you pass the parent to ComputePrevIdFromServer
| |
149 bool position_matches = parent_matches && | |
150 (server_prev_id == entry.Get(syncable::PREV_ID)); | |
ncarter (slow)
2012/01/04 21:37:59
The comment about 'conservative' could be read to
Nicolas Zea
2012/01/18 02:54:39
Upon further reflection, the code as stands actual
| |
151 DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id " | |
152 << " is " << server_prev_id << ", local prev id is " | |
153 << entry.Get(syncable::PREV_ID); | |
141 const sync_pb::EntitySpecifics& specifics = | 154 const sync_pb::EntitySpecifics& specifics = |
142 entry.Get(syncable::SPECIFICS); | 155 entry.Get(syncable::SPECIFICS); |
143 const sync_pb::EntitySpecifics& server_specifics = | 156 const sync_pb::EntitySpecifics& server_specifics = |
144 entry.Get(syncable::SERVER_SPECIFICS); | 157 entry.Get(syncable::SERVER_SPECIFICS); |
145 const sync_pb::EntitySpecifics& base_server_specifics = | 158 const sync_pb::EntitySpecifics& base_server_specifics = |
146 entry.Get(syncable::BASE_SERVER_SPECIFICS); | 159 entry.Get(syncable::BASE_SERVER_SPECIFICS); |
147 std::string decrypted_specifics, decrypted_server_specifics; | 160 std::string decrypted_specifics, decrypted_server_specifics; |
148 bool specifics_match = false; | 161 bool specifics_match = false; |
149 bool server_encrypted_with_default_key = false; | 162 bool server_encrypted_with_default_key = false; |
150 if (specifics.has_encrypted()) { | 163 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 | 224 // We deliberately leave the server's device information. This client will |
212 // add it's own device information on restart. | 225 // add it's own device information on restart. |
213 entry.Put(syncable::SPECIFICS, specifics); | 226 entry.Put(syncable::SPECIFICS, specifics); |
214 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; | 227 DVLOG(1) << "Resovling simple conflict, merging nigori nodes: " << entry; |
215 status->increment_num_server_overwrites(); | 228 status->increment_num_server_overwrites(); |
216 OverwriteServerChanges(trans, &entry); | 229 OverwriteServerChanges(trans, &entry); |
217 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 230 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
218 NIGORI_MERGE, | 231 NIGORI_MERGE, |
219 CONFLICT_RESOLUTION_SIZE); | 232 CONFLICT_RESOLUTION_SIZE); |
220 } else if (!entry_deleted && name_matches && parent_matches && | 233 } else if (!entry_deleted && name_matches && parent_matches && |
221 specifics_match) { | 234 position_matches && specifics_match) { |
222 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " | 235 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
223 << "changes for: " << entry; | 236 << "changes for: " << entry; |
224 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the | 237 // 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 | 238 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset |
226 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data | 239 // 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 | 240 // 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 | 241 // re-inserted into the PREV_ID/NEXT_ID linked list. |
229 // positional info). | |
230 OverwriteServerChanges(trans, &entry); | 242 OverwriteServerChanges(trans, &entry); |
231 IgnoreLocalChanges(&entry); | 243 IgnoreLocalChanges(&entry); |
232 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 244 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
233 CHANGES_MATCH, | 245 CHANGES_MATCH, |
234 CONFLICT_RESOLUTION_SIZE); | 246 CONFLICT_RESOLUTION_SIZE); |
235 } else if (base_server_specifics_match) { | 247 } else if (base_server_specifics_match) { |
236 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " | 248 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " |
237 << " changes for: " << entry; | 249 << " changes for: " << entry; |
238 status->increment_num_server_overwrites(); | 250 status->increment_num_server_overwrites(); |
239 OverwriteServerChanges(trans, &entry); | 251 OverwriteServerChanges(trans, &entry); |
240 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 252 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
241 IGNORE_ENCRYPTION, | 253 IGNORE_ENCRYPTION, |
242 CONFLICT_RESOLUTION_SIZE); | 254 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) { | 255 } else if (entry_deleted || !name_matches || !parent_matches) { |
rlarocque
2012/01/10 23:39:51
Assertion:
If this condition is true, then we woul
Nicolas Zea
2012/01/18 02:54:39
I'm not sure I understand this assertion. The if s
| |
247 OverwriteServerChanges(trans, &entry); | 256 OverwriteServerChanges(trans, &entry); |
248 status->increment_num_server_overwrites(); | 257 status->increment_num_server_overwrites(); |
249 DVLOG(1) << "Resolving simple conflict, overwriting server changes " | 258 DVLOG(1) << "Resolving simple conflict, overwriting server changes " |
250 << "for: " << entry; | 259 << "for: " << entry; |
251 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 260 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
252 OVERWRITE_SERVER, | 261 OVERWRITE_SERVER, |
253 CONFLICT_RESOLUTION_SIZE); | 262 CONFLICT_RESOLUTION_SIZE); |
254 } else { | 263 } else { |
rlarocque
2012/01/10 23:39:51
I believe this is where your change on line 234 st
Nicolas Zea
2012/01/18 02:54:39
See above comment. When the position doesn't match
rlarocque
2012/01/18 20:21:55
As I understand it, most conflicts end up being re
Nicolas Zea
2012/01/18 20:40:42
Okay, I think we're in agreement here then. Yes, t
| |
255 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " | 264 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " |
256 << entry; | 265 << entry; |
257 IgnoreLocalChanges(&entry); | 266 IgnoreLocalChanges(&entry); |
258 status->increment_num_local_overwrites(); | 267 status->increment_num_local_overwrites(); |
259 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 268 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
260 OVERWRITE_LOCAL, | 269 OVERWRITE_LOCAL, |
261 CONFLICT_RESOLUTION_SIZE); | 270 CONFLICT_RESOLUTION_SIZE); |
262 } | 271 } |
272 // Now that we've resolved the conflict, clear the prev server | |
273 // specifics. | |
274 entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); | |
263 return SYNC_PROGRESS; | 275 return SYNC_PROGRESS; |
264 } else { // SERVER_IS_DEL is true | 276 } else { // SERVER_IS_DEL is true |
265 // If a server deleted folder has local contents we should be in a set. | 277 // If a server deleted folder has local contents we should be in a set. |
266 if (entry.Get(syncable::IS_DIR)) { | 278 if (entry.Get(syncable::IS_DIR)) { |
267 Directory::ChildHandles children; | 279 Directory::ChildHandles children; |
268 trans->directory()->GetChildHandlesById(trans, | 280 trans->directory()->GetChildHandlesById(trans, |
269 entry.Get(syncable::ID), | 281 entry.Get(syncable::ID), |
270 &children); | 282 &children); |
271 if (0 != children.size()) { | 283 if (0 != children.size()) { |
272 DVLOG(1) << "Entry is a server deleted directory with local contents, " | 284 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 } | 323 } |
312 | 324 |
313 bool ConflictResolver::ResolveSimpleConflicts( | 325 bool ConflictResolver::ResolveSimpleConflicts( |
314 syncable::WriteTransaction* trans, | 326 syncable::WriteTransaction* trans, |
315 const Cryptographer* cryptographer, | 327 const Cryptographer* cryptographer, |
316 const ConflictProgress& progress, | 328 const ConflictProgress& progress, |
317 sessions::StatusController* status) { | 329 sessions::StatusController* status) { |
318 bool forward_progress = false; | 330 bool forward_progress = false; |
319 // First iterate over simple conflict items (those that belong to no set). | 331 // First iterate over simple conflict items (those that belong to no set). |
320 set<Id>::const_iterator conflicting_item_it; | 332 set<Id>::const_iterator conflicting_item_it; |
333 set<Id> processed_items; | |
321 for (conflicting_item_it = progress.ConflictingItemsBegin(); | 334 for (conflicting_item_it = progress.ConflictingItemsBegin(); |
322 conflicting_item_it != progress.ConflictingItemsEnd(); | 335 conflicting_item_it != progress.ConflictingItemsEnd(); |
323 ++conflicting_item_it) { | 336 ++conflicting_item_it) { |
324 Id id = *conflicting_item_it; | 337 Id id = *conflicting_item_it; |
338 if (processed_items.count(id) > 0) | |
339 continue; | |
325 map<Id, ConflictSet*>::const_iterator item_set_it = | 340 map<Id, ConflictSet*>::const_iterator item_set_it = |
326 progress.IdToConflictSetFind(id); | 341 progress.IdToConflictSetFind(id); |
327 if (item_set_it == progress.IdToConflictSetEnd() || | 342 if (item_set_it == progress.IdToConflictSetEnd() || |
328 0 == item_set_it->second) { | 343 0 == item_set_it->second) { |
329 // We have a simple conflict. | 344 // We have a simple conflict. In order check if positions have changed, |
rlarocque
2012/01/18 20:21:55
I don't understand why this is necessary.
I think
Nicolas Zea
2012/01/18 20:40:42
You're right, we don't modify anything except IS_
| |
330 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { | 345 // we need to process conflicting predecessors before successors. Traverse |
331 case NO_SYNC_PROGRESS: | 346 // backwards through all continuous conflicting predecessors, building a |
347 // stack of items to resolve in predecessor->successor order, then process | |
348 // each item individually. | |
349 list<Id> predecessors; | |
350 Id prev_id = id; | |
351 do { | |
352 predecessors.push_back(prev_id); | |
353 Entry entry(trans, syncable::GET_BY_ID, prev_id); | |
354 // Any entry in conflict must be valid. | |
355 CHECK(entry.good()); | |
356 Id new_prev_id = entry.Get(syncable::PREV_ID); | |
357 if (new_prev_id == prev_id) | |
332 break; | 358 break; |
333 case SYNC_PROGRESS: | 359 prev_id = new_prev_id; |
334 forward_progress = true; | 360 } while (processed_items.count(prev_id) == 0 && |
335 break; | 361 progress.HasSimpleConflictItem(prev_id)); // Excludes root. |
362 while (!predecessors.empty()) { | |
363 id = predecessors.back(); | |
364 predecessors.pop_back(); | |
365 switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { | |
366 case NO_SYNC_PROGRESS: | |
367 break; | |
368 case SYNC_PROGRESS: | |
369 forward_progress = true; | |
370 break; | |
371 } | |
372 processed_items.insert(id); | |
336 } | 373 } |
337 } | 374 } |
338 } | 375 } |
339 return forward_progress; | 376 return forward_progress; |
340 } | 377 } |
341 | 378 |
342 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, | 379 bool ConflictResolver::ResolveConflicts(syncable::WriteTransaction* trans, |
343 const Cryptographer* cryptographer, | 380 const Cryptographer* cryptographer, |
344 const ConflictProgress& progress, | 381 const ConflictProgress& progress, |
345 sessions::StatusController* status) { | 382 sessions::StatusController* status) { |
346 // TODO(rlarocque): A good amount of code related to the resolution of | 383 // 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 | 384 // 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 | 385 // 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. | 386 // 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 | 387 // 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 | 388 // 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. | 389 // that code was trying to do and consider re-enabling parts of it. |
353 | 390 |
354 if (progress.ConflictSetsSize() > 0) { | 391 if (progress.ConflictSetsSize() > 0) { |
355 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() | 392 DVLOG(1) << "Detected " << progress.IdToConflictSetSize() |
356 << " non-simple conflicting entries in " << progress.ConflictSetsSize() | 393 << " non-simple conflicting entries in " << progress.ConflictSetsSize() |
357 << " unprocessed conflict sets."; | 394 << " unprocessed conflict sets."; |
358 } | 395 } |
359 | 396 |
360 return ResolveSimpleConflicts(trans, cryptographer, progress, status); | 397 return ResolveSimpleConflicts(trans, cryptographer, progress, status); |
361 } | 398 } |
362 | 399 |
363 } // namespace browser_sync | 400 } // namespace browser_sync |
OLD | NEW |