1 // Copyright 2013 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.
5 #include "sync/engine/get_commit_ids.h"
10 #include "base/basictypes.h"
11 #include "sync/engine/syncer_util.h"
12 #include "sync/syncable/directory.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"
27 // Forward-declare some helper functions. This gives us more options for
28 // ordering the function defintions within this file.
30 // Filters |unsynced_handles| to remove all entries that do not belong to the
31 // specified |requested_types|, or are not eligible for a commit at this time.
32 void FilterUnreadyEntries(
33 syncable::BaseTransaction
* trans
,
34 ModelTypeSet requested_types
,
35 ModelTypeSet encrypted_types
,
36 bool passphrase_missing
,
37 const syncable::Directory::Metahandles
& unsynced_handles
,
38 std::set
<int64
>* ready_unsynced_set
);
40 // Given a set of commit metahandles that are ready for commit
41 // (|ready_unsynced_set|), sorts these into commit order and places up to
42 // |max_entries| of them in the output parameter |out|.
44 // See the header file for an explanation of commit ordering.
46 syncable::BaseTransaction
* trans
,
48 const std::set
<int64
>& ready_unsynced_set
,
49 std::vector
<int64
>* out
);
53 void GetCommitIdsForType(
54 syncable::BaseTransaction
* trans
,
57 syncable::Directory::Metahandles
* out
) {
58 syncable::Directory
* dir
= trans
->directory();
60 // Gather the full set of unsynced items and store it in the session. They
61 // are not in the correct order for commit.
62 std::set
<int64
> ready_unsynced_set
;
63 syncable::Directory::Metahandles all_unsynced_handles
;
64 GetUnsyncedEntries(trans
, &all_unsynced_handles
);
66 ModelTypeSet encrypted_types
;
67 bool passphrase_missing
= false;
68 Cryptographer
* cryptographer
= dir
->GetCryptographer(trans
);
70 encrypted_types
= dir
->GetNigoriHandler()->GetEncryptedTypes(trans
);
71 passphrase_missing
= cryptographer
->has_pending_keys();
74 // We filter out all unready entries from the set of unsynced handles. This
75 // new set of ready and unsynced items is then what we use to determine what
76 // is a candidate for commit. The caller is responsible for ensuring that no
77 // throttled types are included among the requested_types.
78 FilterUnreadyEntries(trans
,
85 OrderCommitIds(trans
, max_entries
, ready_unsynced_set
, out
);
87 for (size_t i
= 0; i
< out
->size(); i
++) {
88 DVLOG(1) << "Debug commit batch result:" << (*out
)[i
];
94 bool IsEntryInConflict(const syncable::Entry
& entry
) {
95 if (entry
.GetIsUnsynced() &&
96 entry
.GetServerVersion() > 0 &&
97 (entry
.GetServerVersion() > entry
.GetBaseVersion())) {
98 // The local and server versions don't match. The item must be in
99 // conflict, so there's no point in attempting to commit.
100 DCHECK(entry
.GetIsUnappliedUpdate());
101 DVLOG(1) << "Excluding entry from commit due to version mismatch "
108 // Return true if this entry has any attachments that haven't yet been uploaded
110 bool HasAttachmentNotOnServer(const syncable::Entry
& entry
) {
111 const sync_pb::AttachmentMetadata
& metadata
= entry
.GetAttachmentMetadata();
112 for (int i
= 0; i
< metadata
.record_size(); ++i
) {
113 if (!metadata
.record(i
).is_on_server()) {
120 // An entry is not considered ready for commit if any are true:
121 // 1. It's in conflict.
122 // 2. It requires encryption (either the type is encrypted but a passphrase
123 // is missing from the cryptographer, or the entry itself wasn't properly
125 // 3. It's type is currently throttled.
126 // 4. It's a delete but has not been committed.
127 bool IsEntryReadyForCommit(ModelTypeSet requested_types
,
128 ModelTypeSet encrypted_types
,
129 bool passphrase_missing
,
130 const syncable::Entry
& entry
) {
131 DCHECK(entry
.GetIsUnsynced());
132 if (IsEntryInConflict(entry
))
135 const ModelType type
= entry
.GetModelType();
136 // We special case the nigori node because even though it is considered an
137 // "encrypted type", not all nigori node changes require valid encryption
139 if ((type
!= NIGORI
) && encrypted_types
.Has(type
) &&
140 (passphrase_missing
||
141 syncable::EntryNeedsEncryption(encrypted_types
, entry
))) {
142 // This entry requires encryption but is not properly encrypted (possibly
143 // due to the cryptographer not being initialized or the user hasn't
144 // provided the most recent passphrase).
145 DVLOG(1) << "Excluding entry from commit due to lack of encryption "
150 // Ignore it if it's not in our set of requested types.
151 if (!requested_types
.Has(type
))
154 if (entry
.GetIsDel() && !entry
.GetId().ServerKnows()) {
155 // New clients (following the resolution of crbug.com/125381) should not
156 // create such items. Old clients may have left some in the database
157 // (crbug.com/132905), but we should now be cleaning them on startup.
158 NOTREACHED() << "Found deleted and unsynced local item: " << entry
;
162 // Extra validity checks.
163 syncable::Id id
= entry
.GetId();
164 if (id
== entry
.GetParentId()) {
165 CHECK(id
.IsRoot()) << "Non-root item is self parenting." << entry
;
166 // If the root becomes unsynced it can cause us problems.
167 NOTREACHED() << "Root item became unsynced " << entry
;
171 if (entry
.IsRoot()) {
172 NOTREACHED() << "Permanent item became unsynced " << entry
;
176 if (HasAttachmentNotOnServer(entry
)) {
177 // This entry is not ready to be sent to the server because it has one or
178 // more attachments that have not yet been uploaded to the server. The idea
179 // here is avoid propagating an entry with dangling attachment references.
183 DVLOG(2) << "Entry is ready for commit: " << entry
;
187 // Filters |unsynced_handles| to remove all entries that do not belong to the
188 // specified |requested_types|, or are not eligible for a commit at this time.
189 void FilterUnreadyEntries(
190 syncable::BaseTransaction
* trans
,
191 ModelTypeSet requested_types
,
192 ModelTypeSet encrypted_types
,
193 bool passphrase_missing
,
194 const syncable::Directory::Metahandles
& unsynced_handles
,
195 std::set
<int64
>* ready_unsynced_set
) {
196 for (syncable::Directory::Metahandles::const_iterator iter
=
197 unsynced_handles
.begin(); iter
!= unsynced_handles
.end(); ++iter
) {
198 syncable::Entry
entry(trans
, syncable::GET_BY_HANDLE
, *iter
);
199 // TODO(maniscalco): While we check if entry is ready to be committed, we
200 // also need to check that all of its ancestors (parents, transitive) are
201 // ready to be committed. Once attachments can prevent an entry from being
202 // committable, this method must ensure all ancestors are ready for commit
204 if (IsEntryReadyForCommit(requested_types
,
208 ready_unsynced_set
->insert(*iter
);
213 // This class helps to implement OrderCommitIds(). Its members track the
214 // progress of a traversal while its methods extend it. It can return early if
215 // the traversal reaches the desired size before the full traversal is complete.
219 syncable::BaseTransaction
* trans
,
221 syncable::Directory::Metahandles
* out
);
224 // First step of traversal building. Adds non-deleted items in order.
225 void AddCreatesAndMoves(const std::set
<int64
>& ready_unsynced_set
);
227 // Second step of traverals building. Appends deleted items.
228 void AddDeletes(const std::set
<int64
>& ready_unsynced_set
);
231 // The following functions do not modify the traversal directly. They return
232 // their results in the |result| vector instead.
233 bool AddUncommittedParentsAndTheirPredecessors(
234 const std::set
<int64
>& ready_unsynced_set
,
235 const syncable::Entry
& item
,
236 syncable::Directory::Metahandles
* result
) const;
238 void TryAddItem(const std::set
<int64
>& ready_unsynced_set
,
239 const syncable::Entry
& item
,
240 syncable::Directory::Metahandles
* result
) const;
242 void AddItemThenPredecessors(
243 const std::set
<int64
>& ready_unsynced_set
,
244 const syncable::Entry
& item
,
245 syncable::Directory::Metahandles
* result
) const;
247 void AddPredecessorsThenItem(
248 const std::set
<int64
>& ready_unsynced_set
,
249 const syncable::Entry
& item
,
250 syncable::Directory::Metahandles
* result
) const;
252 bool AddDeletedParents(const std::set
<int64
>& ready_unsynced_set
,
253 const syncable::Entry
& item
,
254 const syncable::Directory::Metahandles
& traversed
,
255 syncable::Directory::Metahandles
* result
) const;
257 bool SupportsHierarchy(const syncable::Entry
& item
) const;
259 // Returns true if we've collected enough items.
262 // Returns true if the specified handle is already in the traversal.
263 bool HaveItem(int64 handle
) const;
265 // Adds the specified handles to the traversal.
266 void AppendManyToTraversal(const syncable::Directory::Metahandles
& handles
);
268 // Adds the specifed handle to the traversal.
269 void AppendToTraversal(int64 handle
);
271 syncable::Directory::Metahandles
* out_
;
272 std::set
<int64
> added_handles_
;
273 const size_t max_entries_
;
274 syncable::BaseTransaction
* trans_
;
276 DISALLOW_COPY_AND_ASSIGN(Traversal
);
279 Traversal::Traversal(
280 syncable::BaseTransaction
* trans
,
282 syncable::Directory::Metahandles
* out
)
284 max_entries_(max_entries
),
287 Traversal::~Traversal() {}
289 bool Traversal::AddUncommittedParentsAndTheirPredecessors(
290 const std::set
<int64
>& ready_unsynced_set
,
291 const syncable::Entry
& item
,
292 syncable::Directory::Metahandles
* result
) const {
293 DCHECK(SupportsHierarchy(item
));
294 syncable::Directory::Metahandles dependencies
;
295 syncable::Id parent_id
= item
.GetParentId();
297 // Climb the tree adding entries leaf -> root.
298 while (!parent_id
.ServerKnows()) {
299 syncable::Entry
parent(trans_
, syncable::GET_BY_ID
, parent_id
);
300 CHECK(parent
.good()) << "Bad user-only parent in item path.";
301 int64 handle
= parent
.GetMetahandle();
302 if (HaveItem(handle
)) {
303 // We've already added this parent (and therefore all of its parents).
304 // We can return early.
307 if (IsEntryInConflict(parent
)) {
308 // We ignore all entries that are children of a conflicing item. Return
309 // false immediately to forget the traversal we've built up so far.
310 DVLOG(1) << "Parent was in conflict, omitting " << item
;
313 AddItemThenPredecessors(ready_unsynced_set
,
316 parent_id
= parent
.GetParentId();
319 // Reverse what we added to get the correct order.
320 result
->insert(result
->end(), dependencies
.rbegin(), dependencies
.rend());
324 // Adds the given item to the list if it is unsynced and ready for commit.
325 void Traversal::TryAddItem(const std::set
<int64
>& ready_unsynced_set
,
326 const syncable::Entry
& item
,
327 syncable::Directory::Metahandles
* result
) const {
328 DCHECK(item
.GetIsUnsynced());
329 int64 item_handle
= item
.GetMetahandle();
330 if (ready_unsynced_set
.count(item_handle
) != 0) {
331 result
->push_back(item_handle
);
335 // Adds the given item, and all its unsynced predecessors. The traversal will
336 // be cut short if any item along the traversal is not IS_UNSYNCED, or if we
337 // detect that this area of the tree has already been traversed. Items that are
338 // not 'ready' for commit (see IsEntryReadyForCommit()) will not be added to the
339 // list, though they will not stop the traversal.
340 void Traversal::AddItemThenPredecessors(
341 const std::set
<int64
>& ready_unsynced_set
,
342 const syncable::Entry
& item
,
343 syncable::Directory::Metahandles
* result
) const {
344 int64 item_handle
= item
.GetMetahandle();
345 if (HaveItem(item_handle
)) {
346 // We've already added this item to the commit set, and so must have
347 // already added the predecessors as well.
350 TryAddItem(ready_unsynced_set
, item
, result
);
352 return; // Deleted items have no predecessors.
354 syncable::Id prev_id
= item
.GetPredecessorId();
355 while (!prev_id
.IsNull()) {
356 syncable::Entry
prev(trans_
, syncable::GET_BY_ID
, prev_id
);
357 CHECK(prev
.good()) << "Bad id when walking predecessors.";
358 if (!prev
.GetIsUnsynced()) {
359 // We're interested in "runs" of unsynced items. This item breaks
360 // the streak, so we stop traversing.
363 int64 handle
= prev
.GetMetahandle();
364 if (HaveItem(handle
)) {
365 // We've already added this item to the commit set, and so must have
366 // already added the predecessors as well.
369 TryAddItem(ready_unsynced_set
, prev
, result
);
370 prev_id
= prev
.GetPredecessorId();
374 // Same as AddItemThenPredecessor, but the traversal order will be reversed.
375 void Traversal::AddPredecessorsThenItem(
376 const std::set
<int64
>& ready_unsynced_set
,
377 const syncable::Entry
& item
,
378 syncable::Directory::Metahandles
* result
) const {
379 syncable::Directory::Metahandles dependencies
;
380 AddItemThenPredecessors(ready_unsynced_set
, item
, &dependencies
);
382 // Reverse what we added to get the correct order.
383 result
->insert(result
->end(), dependencies
.rbegin(), dependencies
.rend());
386 // Traverses the tree from bottom to top, adding the deleted parents of the
387 // given |item|. Stops traversing if it encounters a non-deleted node, or
388 // a node that was already listed in the |traversed| list. Returns an error
389 // (false) if a node along the traversal is in a conflict state.
391 // The result list is reversed before it is returned, so the resulting
392 // traversal is in top to bottom order. Also note that this function appends
393 // to the result list without clearing it.
394 bool Traversal::AddDeletedParents(
395 const std::set
<int64
>& ready_unsynced_set
,
396 const syncable::Entry
& item
,
397 const syncable::Directory::Metahandles
& traversed
,
398 syncable::Directory::Metahandles
* result
) const {
399 DCHECK(SupportsHierarchy(item
));
400 syncable::Directory::Metahandles dependencies
;
401 syncable::Id parent_id
= item
.GetParentId();
403 // Climb the tree adding entries leaf -> root.
404 while (!parent_id
.IsRoot()) {
405 syncable::Entry
parent(trans_
, syncable::GET_BY_ID
, parent_id
);
407 if (!parent
.good()) {
408 // This is valid because the parent could have gone away a long time ago
410 // Consider the case where a folder is server-unknown and locally
411 // deleted, and has a child that is server-known, deleted, and unsynced.
412 // The parent could be dropped from memory at any time, but its child
413 // needs to be committed first.
416 int64 handle
= parent
.GetMetahandle();
417 if (!parent
.GetIsUnsynced()) {
418 // In some rare cases, our parent can be both deleted and unsynced.
419 // (ie. the server-unknown parent case).
422 if (!parent
.GetIsDel()) {
423 // We're not intersted in non-deleted parents.
426 if (std::find(traversed
.begin(), traversed
.end(), handle
) !=
428 // We've already added this parent (and therefore all of its parents).
429 // We can return early.
432 if (IsEntryInConflict(parent
)) {
433 // We ignore all entries that are children of a conflicing item. Return
434 // false immediately to forget the traversal we've built up so far.
435 DVLOG(1) << "Parent was in conflict, omitting " << item
;
438 TryAddItem(ready_unsynced_set
, parent
, &dependencies
);
439 parent_id
= parent
.GetParentId();
442 // Reverse what we added to get the correct order.
443 result
->insert(result
->end(), dependencies
.rbegin(), dependencies
.rend());
447 bool Traversal::IsFull() const {
448 return out_
->size() >= max_entries_
;
451 bool Traversal::HaveItem(int64 handle
) const {
452 return added_handles_
.find(handle
) != added_handles_
.end();
455 bool Traversal::SupportsHierarchy(const syncable::Entry
& item
) const {
456 return !item
.GetParentId().IsNull();
459 void Traversal::AppendManyToTraversal(
460 const syncable::Directory::Metahandles
& handles
) {
461 out_
->insert(out_
->end(), handles
.begin(), handles
.end());
462 added_handles_
.insert(handles
.begin(), handles
.end());
465 void Traversal::AppendToTraversal(int64 metahandle
) {
466 out_
->push_back(metahandle
);
467 added_handles_
.insert(metahandle
);
470 void Traversal::AddCreatesAndMoves(
471 const std::set
<int64
>& ready_unsynced_set
) {
472 // Add moves and creates, and prepend their uncommitted parents.
473 for (std::set
<int64
>::const_iterator iter
= ready_unsynced_set
.begin();
474 !IsFull() && iter
!= ready_unsynced_set
.end(); ++iter
) {
475 int64 metahandle
= *iter
;
476 if (HaveItem(metahandle
))
479 syncable::Entry
entry(trans_
,
480 syncable::GET_BY_HANDLE
,
482 if (!entry
.GetIsDel()) {
483 if (SupportsHierarchy(entry
)) {
484 // We only commit an item + its dependencies if it and all its
485 // dependencies are not in conflict.
486 syncable::Directory::Metahandles item_dependencies
;
487 if (AddUncommittedParentsAndTheirPredecessors(ready_unsynced_set
, entry
,
488 &item_dependencies
)) {
489 AddPredecessorsThenItem(ready_unsynced_set
, entry
,
491 AppendManyToTraversal(item_dependencies
);
494 // No hierarchy dependencies, just commit the item itself.
495 AppendToTraversal(metahandle
);
500 // It's possible that we overcommitted while trying to expand dependent
501 // items. If so, truncate the set down to the allowed size.
502 if (out_
->size() > max_entries_
)
503 out_
->resize(max_entries_
);
506 void Traversal::AddDeletes(const std::set
<int64
>& ready_unsynced_set
) {
507 syncable::Directory::Metahandles deletion_list
;
509 // Note: we iterate over all the unsynced set, regardless of the max size.
510 // The max size is only enforced after the top-to-bottom order has been
511 // reversed, in order to ensure children are always deleted before parents.
512 for (std::set
<int64
>::const_iterator iter
= ready_unsynced_set
.begin();
513 iter
!= ready_unsynced_set
.end(); ++iter
) {
514 int64 metahandle
= *iter
;
516 if (HaveItem(metahandle
))
519 if (std::find(deletion_list
.begin(), deletion_list
.end(), metahandle
) !=
520 deletion_list
.end()) {
524 syncable::Entry
entry(trans_
, syncable::GET_BY_HANDLE
,
527 if (entry
.GetIsDel()) {
528 if (SupportsHierarchy(entry
)) {
529 syncable::Directory::Metahandles parents
;
530 if (AddDeletedParents(ready_unsynced_set
, entry
, deletion_list
,
532 // Append parents and chilren in top to bottom order.
533 deletion_list
.insert(deletion_list
.end(), parents
.begin(),
535 deletion_list
.push_back(metahandle
);
538 deletion_list
.push_back(metahandle
);
543 // We've been gathering deletions in top to bottom order. Now we reverse the
544 // order as we prepare the list.
545 std::reverse(deletion_list
.begin(), deletion_list
.end());
546 AppendManyToTraversal(deletion_list
);
548 // It's possible that we overcommitted while trying to expand dependent
549 // items. If so, truncate the set down to the allowed size.
550 if (out_
->size() > max_entries_
)
551 out_
->resize(max_entries_
);
555 syncable::BaseTransaction
* trans
,
557 const std::set
<int64
>& ready_unsynced_set
,
558 syncable::Directory::Metahandles
* out
) {
559 // Commits follow these rules:
560 // 1. Moves or creates are preceded by needed folder creates, from
561 // root to leaf. For folders whose contents are ordered, moves
562 // and creates appear in order.
563 // 2. Moves/Creates before deletes.
564 // 3. Deletes, collapsed.
565 // We commit deleted moves under deleted items as moves when collapsing
568 Traversal
traversal(trans
, max_entries
, out
);
570 // Add moves and creates, and prepend their uncommitted parents.
571 traversal
.AddCreatesAndMoves(ready_unsynced_set
);
574 traversal
.AddDeletes(ready_unsynced_set
);
579 } // namespace syncer