OLD | NEW |
| (Empty) |
1 // Copyright 2012 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 file. | |
4 | |
5 #include "sync/engine/get_commit_ids_command.h" | |
6 | |
7 #include <set> | |
8 #include <utility> | |
9 #include <vector> | |
10 | |
11 #include "sync/engine/syncer_util.h" | |
12 #include "sync/sessions/nudge_tracker.h" | |
13 #include "sync/syncable/entry.h" | |
14 #include "sync/syncable/nigori_handler.h" | |
15 #include "sync/syncable/nigori_util.h" | |
16 #include "sync/syncable/syncable_base_transaction.h" | |
17 #include "sync/syncable/syncable_util.h" | |
18 #include "sync/util/cryptographer.h" | |
19 | |
20 using std::set; | |
21 using std::vector; | |
22 | |
23 namespace syncer { | |
24 | |
25 using sessions::OrderedCommitSet; | |
26 using sessions::SyncSession; | |
27 using sessions::StatusController; | |
28 | |
29 GetCommitIdsCommand::GetCommitIdsCommand( | |
30 syncable::BaseTransaction* trans, | |
31 ModelTypeSet requested_types, | |
32 const size_t commit_batch_size, | |
33 sessions::OrderedCommitSet* commit_set) | |
34 : trans_(trans), | |
35 requested_types_(requested_types), | |
36 requested_commit_batch_size_(commit_batch_size), | |
37 commit_set_(commit_set) { | |
38 } | |
39 | |
40 GetCommitIdsCommand::~GetCommitIdsCommand() {} | |
41 | |
42 SyncerError GetCommitIdsCommand::ExecuteImpl(SyncSession* session) { | |
43 // Gather the full set of unsynced items and store it in the session. They | |
44 // are not in the correct order for commit. | |
45 std::set<int64> ready_unsynced_set; | |
46 syncable::Directory::Metahandles all_unsynced_handles; | |
47 GetUnsyncedEntries(trans_, | |
48 &all_unsynced_handles); | |
49 | |
50 ModelTypeSet encrypted_types; | |
51 bool passphrase_missing = false; | |
52 Cryptographer* cryptographer = | |
53 session->context()-> | |
54 directory()->GetCryptographer(trans_); | |
55 if (cryptographer) { | |
56 encrypted_types = session->context()->directory()->GetNigoriHandler()-> | |
57 GetEncryptedTypes(trans_); | |
58 passphrase_missing = cryptographer->has_pending_keys(); | |
59 }; | |
60 | |
61 // We filter out all unready entries from the set of unsynced handles. This | |
62 // new set of ready and unsynced items is then what we use to determine what | |
63 // is a candidate for commit. The caller of this SyncerCommand is responsible | |
64 // for ensuring that no throttled types are included among the | |
65 // requested_types. | |
66 FilterUnreadyEntries(trans_, | |
67 requested_types_, | |
68 encrypted_types, | |
69 passphrase_missing, | |
70 all_unsynced_handles, | |
71 &ready_unsynced_set); | |
72 | |
73 BuildCommitIds(trans_, | |
74 session->context()->routing_info(), | |
75 ready_unsynced_set); | |
76 | |
77 return SYNCER_OK; | |
78 } | |
79 | |
80 namespace { | |
81 | |
82 bool IsEntryInConflict(const syncable::Entry& entry) { | |
83 if (entry.Get(syncable::IS_UNSYNCED) && | |
84 entry.Get(syncable::SERVER_VERSION) > 0 && | |
85 (entry.Get(syncable::SERVER_VERSION) > | |
86 entry.Get(syncable::BASE_VERSION))) { | |
87 // The local and server versions don't match. The item must be in | |
88 // conflict, so there's no point in attempting to commit. | |
89 DCHECK(entry.Get(syncable::IS_UNAPPLIED_UPDATE)); | |
90 DVLOG(1) << "Excluding entry from commit due to version mismatch " | |
91 << entry; | |
92 return true; | |
93 } | |
94 return false; | |
95 } | |
96 | |
97 // An entry is not considered ready for commit if any are true: | |
98 // 1. It's in conflict. | |
99 // 2. It requires encryption (either the type is encrypted but a passphrase | |
100 // is missing from the cryptographer, or the entry itself wasn't properly | |
101 // encrypted). | |
102 // 3. It's type is currently throttled. | |
103 // 4. It's a delete but has not been committed. | |
104 bool IsEntryReadyForCommit(ModelTypeSet requested_types, | |
105 ModelTypeSet encrypted_types, | |
106 bool passphrase_missing, | |
107 const syncable::Entry& entry) { | |
108 DCHECK(entry.Get(syncable::IS_UNSYNCED)); | |
109 if (IsEntryInConflict(entry)) | |
110 return false; | |
111 | |
112 const ModelType type = entry.GetModelType(); | |
113 // We special case the nigori node because even though it is considered an | |
114 // "encrypted type", not all nigori node changes require valid encryption | |
115 // (ex: sync_tabs). | |
116 if ((type != NIGORI) && encrypted_types.Has(type) && | |
117 (passphrase_missing || | |
118 syncable::EntryNeedsEncryption(encrypted_types, entry))) { | |
119 // This entry requires encryption but is not properly encrypted (possibly | |
120 // due to the cryptographer not being initialized or the user hasn't | |
121 // provided the most recent passphrase). | |
122 DVLOG(1) << "Excluding entry from commit due to lack of encryption " | |
123 << entry; | |
124 return false; | |
125 } | |
126 | |
127 // Ignore it if it's not in our set of requested types. | |
128 if (!requested_types.Has(type)) | |
129 return false; | |
130 | |
131 if (entry.Get(syncable::IS_DEL) && !entry.Get(syncable::ID).ServerKnows()) { | |
132 // New clients (following the resolution of crbug.com/125381) should not | |
133 // create such items. Old clients may have left some in the database | |
134 // (crbug.com/132905), but we should now be cleaning them on startup. | |
135 NOTREACHED() << "Found deleted and unsynced local item: " << entry; | |
136 return false; | |
137 } | |
138 | |
139 // Extra validity checks. | |
140 syncable::Id id = entry.Get(syncable::ID); | |
141 if (id == entry.Get(syncable::PARENT_ID)) { | |
142 CHECK(id.IsRoot()) << "Non-root item is self parenting." << entry; | |
143 // If the root becomes unsynced it can cause us problems. | |
144 NOTREACHED() << "Root item became unsynced " << entry; | |
145 return false; | |
146 } | |
147 | |
148 if (entry.IsRoot()) { | |
149 NOTREACHED() << "Permanent item became unsynced " << entry; | |
150 return false; | |
151 } | |
152 | |
153 DVLOG(2) << "Entry is ready for commit: " << entry; | |
154 return true; | |
155 } | |
156 | |
157 } // namespace | |
158 | |
159 void GetCommitIdsCommand::FilterUnreadyEntries( | |
160 syncable::BaseTransaction* trans, | |
161 ModelTypeSet requested_types, | |
162 ModelTypeSet encrypted_types, | |
163 bool passphrase_missing, | |
164 const syncable::Directory::Metahandles& unsynced_handles, | |
165 std::set<int64>* ready_unsynced_set) { | |
166 for (syncable::Directory::Metahandles::const_iterator iter = | |
167 unsynced_handles.begin(); iter != unsynced_handles.end(); ++iter) { | |
168 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter); | |
169 if (IsEntryReadyForCommit(requested_types, | |
170 encrypted_types, | |
171 passphrase_missing, | |
172 entry)) { | |
173 ready_unsynced_set->insert(*iter); | |
174 } | |
175 } | |
176 } | |
177 | |
178 bool GetCommitIdsCommand::AddUncommittedParentsAndTheirPredecessors( | |
179 syncable::BaseTransaction* trans, | |
180 const ModelSafeRoutingInfo& routes, | |
181 const std::set<int64>& ready_unsynced_set, | |
182 const syncable::Entry& item, | |
183 sessions::OrderedCommitSet* result) const { | |
184 OrderedCommitSet item_dependencies(routes); | |
185 syncable::Id parent_id = item.Get(syncable::PARENT_ID); | |
186 | |
187 // Climb the tree adding entries leaf -> root. | |
188 while (!parent_id.ServerKnows()) { | |
189 syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); | |
190 CHECK(parent.good()) << "Bad user-only parent in item path."; | |
191 int64 handle = parent.Get(syncable::META_HANDLE); | |
192 if (commit_set_->HaveCommitItem(handle)) { | |
193 // We've already added this parent (and therefore all of its parents). | |
194 // We can return early. | |
195 break; | |
196 } | |
197 if (IsEntryInConflict(parent)) { | |
198 // We ignore all entries that are children of a conflicing item. Return | |
199 // false immediately to forget the traversal we've built up so far. | |
200 DVLOG(1) << "Parent was in conflict, omitting " << item; | |
201 return false; | |
202 } | |
203 AddItemThenPredecessors(trans, | |
204 ready_unsynced_set, | |
205 parent, | |
206 &item_dependencies); | |
207 parent_id = parent.Get(syncable::PARENT_ID); | |
208 } | |
209 | |
210 // Reverse what we added to get the correct order. | |
211 result->AppendReverse(item_dependencies); | |
212 return true; | |
213 } | |
214 | |
215 // Adds the given item to the list if it is unsynced and ready for commit. | |
216 void GetCommitIdsCommand::TryAddItem(const std::set<int64>& ready_unsynced_set, | |
217 const syncable::Entry& item, | |
218 OrderedCommitSet* result) const { | |
219 DCHECK(item.Get(syncable::IS_UNSYNCED)); | |
220 int64 item_handle = item.Get(syncable::META_HANDLE); | |
221 if (ready_unsynced_set.count(item_handle) != 0) { | |
222 result->AddCommitItem(item_handle, item.GetModelType()); | |
223 } | |
224 } | |
225 | |
226 // Adds the given item, and all its unsynced predecessors. The traversal will | |
227 // be cut short if any item along the traversal is not IS_UNSYNCED, or if we | |
228 // detect that this area of the tree has already been traversed. Items that are | |
229 // not 'ready' for commit (see IsEntryReadyForCommit()) will not be added to the | |
230 // list, though they will not stop the traversal. | |
231 void GetCommitIdsCommand::AddItemThenPredecessors( | |
232 syncable::BaseTransaction* trans, | |
233 const std::set<int64>& ready_unsynced_set, | |
234 const syncable::Entry& item, | |
235 OrderedCommitSet* result) const { | |
236 int64 item_handle = item.Get(syncable::META_HANDLE); | |
237 if (commit_set_->HaveCommitItem(item_handle)) { | |
238 // We've already added this item to the commit set, and so must have | |
239 // already added the predecessors as well. | |
240 return; | |
241 } | |
242 TryAddItem(ready_unsynced_set, item, result); | |
243 if (item.Get(syncable::IS_DEL)) | |
244 return; // Deleted items have no predecessors. | |
245 | |
246 syncable::Id prev_id = item.GetPredecessorId(); | |
247 while (!prev_id.IsRoot()) { | |
248 syncable::Entry prev(trans, syncable::GET_BY_ID, prev_id); | |
249 CHECK(prev.good()) << "Bad id when walking predecessors."; | |
250 if (!prev.Get(syncable::IS_UNSYNCED)) { | |
251 // We're interested in "runs" of unsynced items. This item breaks | |
252 // the streak, so we stop traversing. | |
253 return; | |
254 } | |
255 int64 handle = prev.Get(syncable::META_HANDLE); | |
256 if (commit_set_->HaveCommitItem(handle)) { | |
257 // We've already added this item to the commit set, and so must have | |
258 // already added the predecessors as well. | |
259 return; | |
260 } | |
261 TryAddItem(ready_unsynced_set, prev, result); | |
262 prev_id = prev.GetPredecessorId(); | |
263 } | |
264 } | |
265 | |
266 // Same as AddItemThenPredecessor, but the traversal order will be reversed. | |
267 void GetCommitIdsCommand::AddPredecessorsThenItem( | |
268 syncable::BaseTransaction* trans, | |
269 const ModelSafeRoutingInfo& routes, | |
270 const std::set<int64>& ready_unsynced_set, | |
271 const syncable::Entry& item, | |
272 OrderedCommitSet* result) const { | |
273 OrderedCommitSet item_dependencies(routes); | |
274 AddItemThenPredecessors(trans, ready_unsynced_set, item, &item_dependencies); | |
275 | |
276 // Reverse what we added to get the correct order. | |
277 result->AppendReverse(item_dependencies); | |
278 } | |
279 | |
280 bool GetCommitIdsCommand::IsCommitBatchFull() const { | |
281 return commit_set_->Size() >= requested_commit_batch_size_; | |
282 } | |
283 | |
284 void GetCommitIdsCommand::AddCreatesAndMoves( | |
285 syncable::BaseTransaction* trans, | |
286 const ModelSafeRoutingInfo& routes, | |
287 const std::set<int64>& ready_unsynced_set) { | |
288 // Add moves and creates, and prepend their uncommitted parents. | |
289 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin(); | |
290 !IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) { | |
291 int64 metahandle = *iter; | |
292 if (commit_set_->HaveCommitItem(metahandle)) | |
293 continue; | |
294 | |
295 syncable::Entry entry(trans, | |
296 syncable::GET_BY_HANDLE, | |
297 metahandle); | |
298 if (!entry.Get(syncable::IS_DEL)) { | |
299 // We only commit an item + its dependencies if it and all its | |
300 // dependencies are not in conflict. | |
301 OrderedCommitSet item_dependencies(routes); | |
302 if (AddUncommittedParentsAndTheirPredecessors( | |
303 trans, | |
304 routes, | |
305 ready_unsynced_set, | |
306 entry, | |
307 &item_dependencies)) { | |
308 AddPredecessorsThenItem(trans, | |
309 routes, | |
310 ready_unsynced_set, | |
311 entry, | |
312 &item_dependencies); | |
313 commit_set_->Append(item_dependencies); | |
314 } | |
315 } | |
316 } | |
317 | |
318 // It's possible that we overcommitted while trying to expand dependent | |
319 // items. If so, truncate the set down to the allowed size. | |
320 commit_set_->Truncate(requested_commit_batch_size_); | |
321 } | |
322 | |
323 void GetCommitIdsCommand::AddDeletes( | |
324 syncable::BaseTransaction* trans, | |
325 const std::set<int64>& ready_unsynced_set) { | |
326 set<syncable::Id> legal_delete_parents; | |
327 | |
328 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin(); | |
329 !IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) { | |
330 int64 metahandle = *iter; | |
331 if (commit_set_->HaveCommitItem(metahandle)) | |
332 continue; | |
333 | |
334 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, | |
335 metahandle); | |
336 | |
337 if (entry.Get(syncable::IS_DEL)) { | |
338 syncable::Entry parent(trans, syncable::GET_BY_ID, | |
339 entry.Get(syncable::PARENT_ID)); | |
340 // If the parent is deleted and unsynced, then any children of that | |
341 // parent don't need to be added to the delete queue. | |
342 // | |
343 // Note: the parent could be synced if there was an update deleting a | |
344 // folder when we had a deleted all items in it. | |
345 // We may get more updates, or we may want to delete the entry. | |
346 if (parent.good() && | |
347 parent.Get(syncable::IS_DEL) && | |
348 parent.Get(syncable::IS_UNSYNCED)) { | |
349 // However, if an entry is moved, these rules can apply differently. | |
350 // | |
351 // If the entry was moved, then the destination parent was deleted, | |
352 // then we'll miss it in the roll up. We have to add it in manually. | |
353 // TODO(chron): Unit test for move / delete cases: | |
354 // Case 1: Locally moved, then parent deleted | |
355 // Case 2: Server moved, then locally issue recursive delete. | |
356 if (entry.Get(syncable::ID).ServerKnows() && | |
357 entry.Get(syncable::PARENT_ID) != | |
358 entry.Get(syncable::SERVER_PARENT_ID)) { | |
359 DVLOG(1) << "Inserting moved and deleted entry, will be missed by " | |
360 << "delete roll." << entry.Get(syncable::ID); | |
361 | |
362 commit_set_->AddCommitItem(metahandle, entry.GetModelType()); | |
363 } | |
364 | |
365 // Skip this entry since it's a child of a parent that will be | |
366 // deleted. The server will unroll the delete and delete the | |
367 // child as well. | |
368 continue; | |
369 } | |
370 | |
371 legal_delete_parents.insert(entry.Get(syncable::PARENT_ID)); | |
372 } | |
373 } | |
374 | |
375 // We could store all the potential entries with a particular parent during | |
376 // the above scan, but instead we rescan here. This is less efficient, but | |
377 // we're dropping memory alloc/dealloc in favor of linear scans of recently | |
378 // examined entries. | |
379 // | |
380 // Scan through the UnsyncedMetaHandles again. If we have a deleted | |
381 // entry, then check if the parent is in legal_delete_parents. | |
382 // | |
383 // Parent being in legal_delete_parents means for the child: | |
384 // a recursive delete is not currently happening (no recent deletes in same | |
385 // folder) | |
386 // parent did expect at least one old deleted child | |
387 // parent was not deleted | |
388 for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin(); | |
389 !IsCommitBatchFull() && iter != ready_unsynced_set.end(); ++iter) { | |
390 int64 metahandle = *iter; | |
391 if (commit_set_->HaveCommitItem(metahandle)) | |
392 continue; | |
393 syncable::Entry entry(trans, syncable::GET_BY_HANDLE, | |
394 metahandle); | |
395 if (entry.Get(syncable::IS_DEL)) { | |
396 syncable::Id parent_id = entry.Get(syncable::PARENT_ID); | |
397 if (legal_delete_parents.count(parent_id)) { | |
398 commit_set_->AddCommitItem(metahandle, entry.GetModelType()); | |
399 } | |
400 } | |
401 } | |
402 } | |
403 | |
404 void GetCommitIdsCommand::BuildCommitIds( | |
405 syncable::BaseTransaction* trans, | |
406 const ModelSafeRoutingInfo& routes, | |
407 const std::set<int64>& ready_unsynced_set) { | |
408 // Commits follow these rules: | |
409 // 1. Moves or creates are preceded by needed folder creates, from | |
410 // root to leaf. For folders whose contents are ordered, moves | |
411 // and creates appear in order. | |
412 // 2. Moves/Creates before deletes. | |
413 // 3. Deletes, collapsed. | |
414 // We commit deleted moves under deleted items as moves when collapsing | |
415 // delete trees. | |
416 | |
417 // Add moves and creates, and prepend their uncommitted parents. | |
418 AddCreatesAndMoves(trans, routes, ready_unsynced_set); | |
419 | |
420 // Add all deletes. | |
421 AddDeletes(trans, ready_unsynced_set); | |
422 } | |
423 | |
424 } // namespace syncer | |
OLD | NEW |