Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(45)

Side by Side Diff: sync/engine/get_commit_ids_command.cc

Issue 23809005: sync: Implement per-type GetCommitIds (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Remove unnecessary whitespace Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698