Index: chrome/browser/sync/engine/conflict_resolver.cc |
diff --git a/chrome/browser/sync/engine/conflict_resolver.cc b/chrome/browser/sync/engine/conflict_resolver.cc |
index cca0c31d8939b42b18b9cd0db87ec8e9c1230eb8..b596a9a7204565e27d87bee444a1689449f7df87 100644 |
--- a/chrome/browser/sync/engine/conflict_resolver.cc |
+++ b/chrome/browser/sync/engine/conflict_resolver.cc |
@@ -5,6 +5,7 @@ |
#include "chrome/browser/sync/engine/conflict_resolver.h" |
#include <algorithm> |
+#include <list> |
#include <map> |
#include <set> |
@@ -19,6 +20,7 @@ |
#include "chrome/browser/sync/syncable/syncable.h" |
#include "chrome/browser/sync/util/cryptographer.h" |
+using std::list; |
using std::map; |
using std::set; |
using syncable::BaseTransaction; |
@@ -110,7 +112,7 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
// b) All unsynced changes have been re-encrypted with the default key ( |
// occurs either in AttemptToUpdateEntry, SetPassphrase, or |
// RefreshEncryption). |
- // c) Prev_server_specifics having a valid datatype means that we received |
+ // c) Base_server_specifics having a valid datatype means that we received |
// an undecryptable update that only changed specifics, and since then have |
// not received any further non-specifics-only or decryptable updates. |
// d) If the server_specifics match specifics, server_specifics are |
@@ -123,9 +125,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
// f) Otherwise, it's in general safer to ignore local changes, with the |
// exception of deletion conflicts (choose to undelete) and conflicts |
// where the non_unique_name or parent don't match. |
- // TODO(sync): We can't compare position here without performing the |
- // NEXT_ID/PREV_ID -> position_in_parent interpolation. Fix that, and take it |
- // into account. |
if (!entry.Get(syncable::SERVER_IS_DEL)) { |
// TODO(nick): The current logic is arbitrary; instead, it ought to be made |
// consistent with the ModelAssociator behavior for a datatype. It would |
@@ -136,8 +135,22 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
bool parent_matches = entry.Get(syncable::PARENT_ID) == |
- entry.Get(syncable::SERVER_PARENT_ID); |
+ entry.Get(syncable::SERVER_PARENT_ID); |
bool entry_deleted = entry.Get(syncable::IS_DEL); |
+ // This positional check is conservative. It will likely be false if the |
+ // predecessor is unsynced/unapplied. For this reason, we ensure conflicting |
+ // predecessors are resolved before their successors. Being |
+ // conservative results in being less likely to lose local positioning |
+ // changes, with the cost of occasional unnecessary commits. |
+ // TODO(zea): change this once we can directly compare server position to |
+ // client position. |
+ 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
|
+ 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
|
+ bool position_matches = parent_matches && |
+ (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
|
+ DVLOG_IF(1, !position_matches) << "Position doesn't match, server prev id " |
+ << " is " << server_prev_id << ", local prev id is " |
+ << entry.Get(syncable::PREV_ID); |
const sync_pb::EntitySpecifics& specifics = |
entry.Get(syncable::SPECIFICS); |
const sync_pb::EntitySpecifics& server_specifics = |
@@ -218,15 +231,14 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
NIGORI_MERGE, |
CONFLICT_RESOLUTION_SIZE); |
} else if (!entry_deleted && name_matches && parent_matches && |
- specifics_match) { |
+ position_matches && specifics_match) { |
DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
<< "changes for: " << entry; |
// This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the |
// BASE_VERSION to match the SERVER_VERSION. If we didn't also unset |
- // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data |
- // when the server update gets applied and the item is re-inserted into |
- // the PREV_ID/NEXT_ID linked list (see above TODO about comparing |
- // positional info). |
+ // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data from |
+ // adjacent entries when the server update gets applied and the item is |
+ // re-inserted into the PREV_ID/NEXT_ID linked list. |
OverwriteServerChanges(trans, &entry); |
IgnoreLocalChanges(&entry); |
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
@@ -240,9 +252,6 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
IGNORE_ENCRYPTION, |
CONFLICT_RESOLUTION_SIZE); |
- // Now that we've resolved the conflict, clear the prev server |
- // specifics. |
- entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); |
} 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
|
OverwriteServerChanges(trans, &entry); |
status->increment_num_server_overwrites(); |
@@ -260,6 +269,9 @@ ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
OVERWRITE_LOCAL, |
CONFLICT_RESOLUTION_SIZE); |
} |
+ // Now that we've resolved the conflict, clear the prev server |
+ // specifics. |
+ entry.Put(syncable::BASE_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); |
return SYNC_PROGRESS; |
} else { // SERVER_IS_DEL is true |
// If a server deleted folder has local contents we should be in a set. |
@@ -318,21 +330,46 @@ bool ConflictResolver::ResolveSimpleConflicts( |
bool forward_progress = false; |
// First iterate over simple conflict items (those that belong to no set). |
set<Id>::const_iterator conflicting_item_it; |
+ set<Id> processed_items; |
for (conflicting_item_it = progress.ConflictingItemsBegin(); |
conflicting_item_it != progress.ConflictingItemsEnd(); |
++conflicting_item_it) { |
Id id = *conflicting_item_it; |
+ if (processed_items.count(id) > 0) |
+ continue; |
map<Id, ConflictSet*>::const_iterator item_set_it = |
progress.IdToConflictSetFind(id); |
if (item_set_it == progress.IdToConflictSetEnd() || |
0 == item_set_it->second) { |
- // We have a simple conflict. |
- switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { |
- case NO_SYNC_PROGRESS: |
- break; |
- case SYNC_PROGRESS: |
- forward_progress = true; |
+ // 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_
|
+ // we need to process conflicting predecessors before successors. Traverse |
+ // backwards through all continuous conflicting predecessors, building a |
+ // stack of items to resolve in predecessor->successor order, then process |
+ // each item individually. |
+ list<Id> predecessors; |
+ Id prev_id = id; |
+ do { |
+ predecessors.push_back(prev_id); |
+ Entry entry(trans, syncable::GET_BY_ID, prev_id); |
+ // Any entry in conflict must be valid. |
+ CHECK(entry.good()); |
+ Id new_prev_id = entry.Get(syncable::PREV_ID); |
+ if (new_prev_id == prev_id) |
break; |
+ prev_id = new_prev_id; |
+ } while (processed_items.count(prev_id) == 0 && |
+ progress.HasSimpleConflictItem(prev_id)); // Excludes root. |
+ while (!predecessors.empty()) { |
+ id = predecessors.back(); |
+ predecessors.pop_back(); |
+ switch (ProcessSimpleConflict(trans, id, cryptographer, status)) { |
+ case NO_SYNC_PROGRESS: |
+ break; |
+ case SYNC_PROGRESS: |
+ forward_progress = true; |
+ break; |
+ } |
+ processed_items.insert(id); |
} |
} |
} |