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 <map> | 8 #include <map> |
9 #include <set> | 9 #include <set> |
10 | 10 |
11 #include "base/location.h" | 11 #include "base/location.h" |
12 #include "base/metrics/histogram.h" | 12 #include "base/metrics/histogram.h" |
13 #include "chrome/browser/sync/engine/syncer.h" | 13 #include "chrome/browser/sync/engine/syncer.h" |
14 #include "chrome/browser/sync/engine/syncer_util.h" | 14 #include "chrome/browser/sync/engine/syncer_util.h" |
15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" | 15 #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" |
16 #include "chrome/browser/sync/protocol/service_constants.h" | 16 #include "chrome/browser/sync/protocol/service_constants.h" |
17 #include "chrome/browser/sync/sessions/status_controller.h" | 17 #include "chrome/browser/sync/sessions/status_controller.h" |
18 #include "chrome/browser/sync/syncable/directory_manager.h" | 18 #include "chrome/browser/sync/syncable/directory_manager.h" |
19 #include "chrome/browser/sync/syncable/syncable.h" | 19 #include "chrome/browser/sync/syncable/syncable.h" |
20 #include "chrome/browser/sync/util/cryptographer.h" | 20 #include "chrome/browser/sync/util/cryptographer.h" |
21 | 21 |
22 using std::map; | 22 using std::map; |
23 using std::set; | 23 using std::set; |
24 using syncable::BaseTransaction; | 24 using syncable::BaseTransaction; |
25 using syncable::Directory; | 25 using syncable::Directory; |
26 using syncable::Entry; | 26 using syncable::Entry; |
27 using syncable::GetModelTypeFromSpecifics; | |
27 using syncable::Id; | 28 using syncable::Id; |
29 using syncable::IsRealDataType; | |
28 using syncable::MutableEntry; | 30 using syncable::MutableEntry; |
29 using syncable::ScopedDirLookup; | 31 using syncable::ScopedDirLookup; |
30 using syncable::WriteTransaction; | 32 using syncable::WriteTransaction; |
31 | 33 |
32 namespace browser_sync { | 34 namespace browser_sync { |
33 | 35 |
34 using sessions::ConflictProgress; | 36 using sessions::ConflictProgress; |
35 using sessions::StatusController; | 37 using sessions::StatusController; |
36 | 38 |
37 namespace { | 39 namespace { |
38 | 40 |
39 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; | 41 const int SYNC_CYCLES_BEFORE_ADMITTING_DEFEAT = 8; |
40 | 42 |
41 // Enumeration of different conflict resolutions. Used for histogramming. | |
42 enum SimpleConflictResolutions { | |
43 OVERWRITE_LOCAL, // Resolved by overwriting local changes. | |
44 OVERWRITE_SERVER, // Resolved by overwriting server changes. | |
45 UNDELETE, // Resolved by undeleting local item. | |
46 IGNORE_ENCRYPTION, // Resolved by ignoring an encryption-only server | |
47 // change. TODO(zea): implement and use this. | |
48 NIGORI_MERGE, // Resolved by merging nigori nodes. | |
49 CONFLICT_RESOLUTION_SIZE, | |
50 }; | |
51 | |
52 } // namespace | 43 } // namespace |
53 | 44 |
54 ConflictResolver::ConflictResolver() { | 45 ConflictResolver::ConflictResolver() { |
55 } | 46 } |
56 | 47 |
57 ConflictResolver::~ConflictResolver() { | 48 ConflictResolver::~ConflictResolver() { |
58 } | 49 } |
59 | 50 |
60 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { | 51 void ConflictResolver::IgnoreLocalChanges(MutableEntry* entry) { |
61 // An update matches local actions, merge the changes. | 52 // An update matches local actions, merge the changes. |
62 // This is a little fishy because we don't actually merge them. | 53 // This is a little fishy because we don't actually merge them. |
63 // In the future we should do a 3-way merge. | 54 // In the future we should do a 3-way merge. |
64 DVLOG(1) << "Resolving conflict by ignoring local changes:" << entry; | |
65 // With IS_UNSYNCED false, changes should be merged. | 55 // With IS_UNSYNCED false, changes should be merged. |
66 entry->Put(syncable::IS_UNSYNCED, false); | 56 entry->Put(syncable::IS_UNSYNCED, false); |
67 } | 57 } |
68 | 58 |
69 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, | 59 void ConflictResolver::OverwriteServerChanges(WriteTransaction* trans, |
70 MutableEntry * entry) { | 60 MutableEntry * entry) { |
71 // This is similar to an overwrite from the old client. | 61 // This is similar to an overwrite from the old client. |
72 // This is equivalent to a scenario where we got the update before we'd | 62 // This is equivalent to a scenario where we got the update before we'd |
73 // made our local client changes. | 63 // made our local client changes. |
74 // TODO(chron): This is really a general property clobber. We clobber | 64 // TODO(chron): This is really a general property clobber. We clobber |
75 // the server side property. Perhaps we should actually do property merging. | 65 // the server side property. Perhaps we should actually do property merging. |
76 DVLOG(1) << "Resolving conflict by ignoring server changes:" << entry; | |
77 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); | 66 entry->Put(syncable::BASE_VERSION, entry->Get(syncable::SERVER_VERSION)); |
78 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); | 67 entry->Put(syncable::IS_UNAPPLIED_UPDATE, false); |
79 } | 68 } |
80 | 69 |
81 ConflictResolver::ProcessSimpleConflictResult | 70 ConflictResolver::ProcessSimpleConflictResult |
82 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, | 71 ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, |
83 const Id& id, | 72 const Id& id, |
84 const Cryptographer* cryptographer, | 73 const Cryptographer* cryptographer, |
85 StatusController* status) { | 74 StatusController* status) { |
86 MutableEntry entry(trans, syncable::GET_BY_ID, id); | 75 MutableEntry entry(trans, syncable::GET_BY_ID, id); |
(...skipping 20 matching lines...) Expand all Loading... | |
107 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { | 96 if (entry.Get(syncable::IS_DEL) && entry.Get(syncable::SERVER_IS_DEL)) { |
108 // we've both deleted it, so lets just drop the need to commit/update this | 97 // we've both deleted it, so lets just drop the need to commit/update this |
109 // entry. | 98 // entry. |
110 entry.Put(syncable::IS_UNSYNCED, false); | 99 entry.Put(syncable::IS_UNSYNCED, false); |
111 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); | 100 entry.Put(syncable::IS_UNAPPLIED_UPDATE, false); |
112 // we've made changes, but they won't help syncing progress. | 101 // we've made changes, but they won't help syncing progress. |
113 // METRIC simple conflict resolved by merge. | 102 // METRIC simple conflict resolved by merge. |
114 return NO_SYNC_PROGRESS; | 103 return NO_SYNC_PROGRESS; |
115 } | 104 } |
116 | 105 |
106 // 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: | |
108 // a) We can decrypt both the local and server data (else we'd be in | |
109 // conflict encryption and not attempting to resolve). | |
tim (not reviewing)
2011/12/15 20:55:11
In the case we just discussed offline (two clients
Nicolas Zea
2011/12/16 19:25:14
Yes, depending on the consistency at the server on
| |
110 // b) All unsynced changes have been re-encrypted with the default key ( | |
111 // occurs either in AttemptToUpdateEntry, SetPassphrase, or | |
112 // RefreshEncryption). | |
113 // c) Prev_server_specifics having a valid datatype means that we received | |
114 // an undecryptable update that only changed specifics, and since then have | |
115 // not received any further non-specifics-only or decryptable updates. | |
116 // d) If the server_specifics match specifics, server_specifics are | |
117 // encrypted with the default key, and all other visible properties match, | |
118 // then we can safely ignore the local changes as redundant. | |
119 // e) Otherwise if the prev_server_specifics match the server_specifics, no | |
120 // functional change must have been made server-side (else | |
121 // prev_server_specifics would have been cleared), and we can therefore | |
122 // safely ignore the server changes as redundant. | |
123 // f) Otherwise, it's in general safer to ignore local changes, with the | |
124 // exception of deletion conflicts (choose to undelete) and conflicts | |
125 // 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. | |
117 if (!entry.Get(syncable::SERVER_IS_DEL)) { | 129 if (!entry.Get(syncable::SERVER_IS_DEL)) { |
118 // This logic determines "client wins" vs. "server wins" strategy picking. | |
119 // TODO(nick): The current logic is arbitrary; instead, it ought to be made | 130 // TODO(nick): The current logic is arbitrary; instead, it ought to be made |
120 // consistent with the ModelAssociator behavior for a datatype. It would | 131 // consistent with the ModelAssociator behavior for a datatype. It would |
121 // be nice if we could route this back to ModelAssociator code to pick one | 132 // be nice if we could route this back to ModelAssociator code to pick one |
122 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) | 133 // of three options: CLIENT, SERVER, or MERGE. Some datatypes (autofill) |
123 // are easily mergeable. | 134 // are easily mergeable. |
124 // See http://crbug.com/77339. | 135 // See http://crbug.com/77339. |
125 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == | 136 bool name_matches = entry.Get(syncable::NON_UNIQUE_NAME) == |
126 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); | 137 entry.Get(syncable::SERVER_NON_UNIQUE_NAME); |
127 bool parent_matches = entry.Get(syncable::PARENT_ID) == | 138 bool parent_matches = entry.Get(syncable::PARENT_ID) == |
128 entry.Get(syncable::SERVER_PARENT_ID); | 139 entry.Get(syncable::SERVER_PARENT_ID); |
129 bool entry_deleted = entry.Get(syncable::IS_DEL); | 140 bool entry_deleted = entry.Get(syncable::IS_DEL); |
141 const sync_pb::EntitySpecifics& specifics = | |
142 entry.Get(syncable::SPECIFICS); | |
143 const sync_pb::EntitySpecifics& server_specifics = | |
144 entry.Get(syncable::SERVER_SPECIFICS); | |
145 const sync_pb::EntitySpecifics& prev_server_specifics = | |
146 entry.Get(syncable::PREV_SERVER_SPECIFICS); | |
147 std::string decrypted_specifics, decrypted_server_specifics; | |
148 bool specifics_match = false; | |
149 bool server_encrypted_with_default_key = false; | |
150 if (specifics.has_encrypted()) { | |
151 DCHECK(cryptographer->CanDecryptUsingDefaultKey(specifics.encrypted())); | |
152 decrypted_specifics = cryptographer->DecryptToString( | |
153 specifics.encrypted()); | |
154 } else { | |
155 decrypted_specifics = specifics.SerializeAsString(); | |
156 } | |
157 if (server_specifics.has_encrypted()) { | |
158 server_encrypted_with_default_key = | |
159 cryptographer->CanDecryptUsingDefaultKey( | |
160 server_specifics.encrypted()); | |
161 decrypted_server_specifics = cryptographer->DecryptToString( | |
162 server_specifics.encrypted()); | |
163 } else { | |
164 decrypted_server_specifics = server_specifics.SerializeAsString(); | |
165 } | |
166 if (decrypted_server_specifics == decrypted_specifics && | |
167 server_encrypted_with_default_key == specifics.has_encrypted()) { | |
168 specifics_match = true; | |
169 } | |
170 bool prev_server_specifics_match = false; | |
171 if (server_specifics.has_encrypted() && | |
172 IsRealDataType(GetModelTypeFromSpecifics(prev_server_specifics))) { | |
173 std::string decrypted_prev_server_specifics; | |
174 if (!prev_server_specifics.has_encrypted()) { | |
175 decrypted_prev_server_specifics = | |
176 prev_server_specifics.SerializeAsString(); | |
177 } else { | |
178 decrypted_prev_server_specifics = cryptographer->DecryptToString( | |
179 prev_server_specifics.encrypted()); | |
180 } | |
181 if (decrypted_server_specifics == decrypted_prev_server_specifics) | |
182 prev_server_specifics_match = true; | |
183 } | |
130 | 184 |
131 // We manually merge nigori data. | 185 // We manually merge nigori data. |
132 // TODO(zea): Find a better way of doing this. As it stands, we have to | 186 // TODO(zea): Find a better way of doing this. As it stands, we have to |
133 // update this code whenever we add a new non-cryptographer related field to | 187 // update this code whenever we add a new non-cryptographer related field to |
134 // the nigori node. | 188 // the nigori node. |
135 if (entry.GetModelType() == syncable::NIGORI) { | 189 if (entry.GetModelType() == syncable::NIGORI) { |
136 // Create a new set of specifics based on the server specifics (which | 190 // Create a new set of specifics based on the server specifics (which |
137 // preserves their encryption keys). | 191 // preserves their encryption keys). |
138 sync_pb::EntitySpecifics specifics = | 192 sync_pb::EntitySpecifics specifics = |
139 entry.Get(syncable::SERVER_SPECIFICS); | 193 entry.Get(syncable::SERVER_SPECIFICS); |
(...skipping 11 matching lines...) Expand all Loading... | |
151 cryptographer->GetKeys(nigori->mutable_encrypted()); | 205 cryptographer->GetKeys(nigori->mutable_encrypted()); |
152 } | 206 } |
153 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori) | 207 if (entry.Get(syncable::SPECIFICS).GetExtension(sync_pb::nigori) |
154 .sync_tabs()) { | 208 .sync_tabs()) { |
155 nigori->set_sync_tabs(true); | 209 nigori->set_sync_tabs(true); |
156 } | 210 } |
157 OverwriteServerChanges(trans, &entry); | 211 OverwriteServerChanges(trans, &entry); |
158 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 212 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
159 NIGORI_MERGE, | 213 NIGORI_MERGE, |
160 CONFLICT_RESOLUTION_SIZE); | 214 CONFLICT_RESOLUTION_SIZE); |
161 } else if (!entry_deleted && name_matches && parent_matches) { | 215 } else if (!entry_deleted && name_matches && parent_matches && |
162 // TODO(zea): We may prefer to choose the local changes over the server | 216 specifics_match) { |
163 // if we know the local changes happened before (or vice versa). | 217 DVLOG(1) << "Resolving simple conflict, everything matches, ignoring " |
164 // See http://crbug.com/76596 | 218 << "changes for: " << entry; |
165 DVLOG(1) << "Resolving simple conflict, ignoring local changes for:" | 219 // This unsets both IS_UNSYNCED and IS_UNAPPLIED_UPDATE, and sets the |
220 // BASE_VERSION to match the SERVER_VERSION. If we didn't also unset | |
221 // IS_UNAPPLIED_UPDATE, then we would lose unsynced positional data | |
222 // when the server update gets applied and the item is re-inserted into | |
223 // the PREV_ID/NEXT_ID linked list (see above TODO about comparing | |
224 // positional info). | |
225 OverwriteServerChanges(trans, &entry); | |
226 IgnoreLocalChanges(&entry); | |
227 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
228 CHANGES_MATCH, | |
229 CONFLICT_RESOLUTION_SIZE); | |
230 } else if (prev_server_specifics_match) { | |
231 DVLOG(1) << "Resolving simple conflict, ignoring server encryption " | |
232 << " changes for: " << entry; | |
233 status->increment_num_server_overwrites(); | |
234 OverwriteServerChanges(trans, &entry); | |
235 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
236 IGNORE_ENCRYPTION, | |
237 CONFLICT_RESOLUTION_SIZE); | |
238 // Now that we've resolved the conflict, clear the prev server | |
239 // specifics. | |
240 entry.Put(syncable::PREV_SERVER_SPECIFICS, sync_pb::EntitySpecifics()); | |
241 } else if (entry_deleted || !name_matches || !parent_matches) { | |
242 OverwriteServerChanges(trans, &entry); | |
243 status->increment_num_server_overwrites(); | |
244 DVLOG(1) << "Resolving simple conflict, overwriting server changes " | |
245 << "for: " << entry; | |
246 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
247 OVERWRITE_SERVER, | |
248 CONFLICT_RESOLUTION_SIZE); | |
249 } else { | |
250 DVLOG(1) << "Resolving simple conflict, ignoring local changes for: " | |
166 << entry; | 251 << entry; |
167 IgnoreLocalChanges(&entry); | 252 IgnoreLocalChanges(&entry); |
168 status->increment_num_local_overwrites(); | 253 status->increment_num_local_overwrites(); |
169 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 254 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
170 OVERWRITE_LOCAL, | 255 OVERWRITE_LOCAL, |
171 CONFLICT_RESOLUTION_SIZE); | 256 CONFLICT_RESOLUTION_SIZE); |
172 } else { | |
173 DVLOG(1) << "Resolving simple conflict, overwriting server changes for:" | |
174 << entry; | |
175 OverwriteServerChanges(trans, &entry); | |
176 status->increment_num_server_overwrites(); | |
177 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | |
178 OVERWRITE_SERVER, | |
179 CONFLICT_RESOLUTION_SIZE); | |
180 } | 257 } |
181 return SYNC_PROGRESS; | 258 return SYNC_PROGRESS; |
182 } else { // SERVER_IS_DEL is true | 259 } else { // SERVER_IS_DEL is true |
183 // If a server deleted folder has local contents we should be in a set. | 260 // If a server deleted folder has local contents we should be in a set. |
184 if (entry.Get(syncable::IS_DIR)) { | 261 if (entry.Get(syncable::IS_DIR)) { |
185 Directory::ChildHandles children; | 262 Directory::ChildHandles children; |
186 trans->directory()->GetChildHandlesById(trans, | 263 trans->directory()->GetChildHandlesById(trans, |
187 entry.Get(syncable::ID), | 264 entry.Get(syncable::ID), |
188 &children); | 265 &children); |
189 if (0 != children.size()) { | 266 if (0 != children.size()) { |
190 DVLOG(1) << "Entry is a server deleted directory with local contents, " | 267 DVLOG(1) << "Entry is a server deleted directory with local contents, " |
191 << "should be in a set. (race condition)."; | 268 << "should be in a set. (race condition)."; |
192 return NO_SYNC_PROGRESS; | 269 return NO_SYNC_PROGRESS; |
193 } | 270 } |
194 } | 271 } |
195 | 272 |
196 // The entry is deleted on the server but still exists locally. | 273 // The entry is deleted on the server but still exists locally. |
197 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { | 274 if (!entry.Get(syncable::UNIQUE_CLIENT_TAG).empty()) { |
198 // If we've got a client-unique tag, we can undelete while retaining | 275 // If we've got a client-unique tag, we can undelete while retaining |
199 // our present ID. | 276 // our present ID. |
200 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " | 277 DCHECK_EQ(entry.Get(syncable::SERVER_VERSION), 0) << "For the server to " |
201 "know to re-create, client-tagged items should revert to version 0 " | 278 "know to re-create, client-tagged items should revert to version 0 " |
202 "when server-deleted."; | 279 "when server-deleted."; |
203 OverwriteServerChanges(trans, &entry); | 280 OverwriteServerChanges(trans, &entry); |
204 status->increment_num_server_overwrites(); | 281 status->increment_num_server_overwrites(); |
282 DVLOG(1) << "Resolving simple conflict, undeleting server entry: " | |
283 << entry; | |
205 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", | 284 UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", |
206 OVERWRITE_SERVER, | 285 OVERWRITE_SERVER, |
207 CONFLICT_RESOLUTION_SIZE); | 286 CONFLICT_RESOLUTION_SIZE); |
208 // Clobber the versions, just in case the above DCHECK is violated. | 287 // Clobber the versions, just in case the above DCHECK is violated. |
209 entry.Put(syncable::SERVER_VERSION, 0); | 288 entry.Put(syncable::SERVER_VERSION, 0); |
210 entry.Put(syncable::BASE_VERSION, 0); | 289 entry.Put(syncable::BASE_VERSION, 0); |
211 } else { | 290 } else { |
212 // Otherwise, we've got to undelete by creating a new locally | 291 // Otherwise, we've got to undelete by creating a new locally |
213 // uncommitted entry. | 292 // uncommitted entry. |
214 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); | 293 SyncerUtil::SplitServerInformationIntoNewEntry(trans, &entry); |
(...skipping 359 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
574 conflict_set_count_map_.erase(i++); | 653 conflict_set_count_map_.erase(i++); |
575 // METRIC self resolved conflict sets ++. | 654 // METRIC self resolved conflict sets ++. |
576 } else { | 655 } else { |
577 ++i; | 656 ++i; |
578 } | 657 } |
579 } | 658 } |
580 return rv; | 659 return rv; |
581 } | 660 } |
582 | 661 |
583 } // namespace browser_sync | 662 } // namespace browser_sync |
OLD | NEW |