• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "sync/engine/get_commit_ids.h"
6 
7 #include <set>
8 #include <vector>
9 
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"
19 
20 using std::set;
21 using std::vector;
22 
23 namespace syncer {
24 
25 namespace {
26 
27 // Forward-declare some helper functions.  This gives us more options for
28 // ordering the function defintions within this file.
29 
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);
39 
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|.
43 //
44 // See the header file for an explanation of commit ordering.
45 void OrderCommitIds(
46     syncable::BaseTransaction* trans,
47     size_t max_entries,
48     const std::set<int64>& ready_unsynced_set,
49     std::vector<int64>* out);
50 
51 }  // namespace
52 
GetCommitIdsForType(syncable::BaseTransaction * trans,ModelType type,size_t max_entries,syncable::Directory::Metahandles * out)53 void GetCommitIdsForType(
54     syncable::BaseTransaction* trans,
55     ModelType type,
56     size_t max_entries,
57     syncable::Directory::Metahandles* out) {
58   syncable::Directory* dir = trans->directory();
59 
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);
65 
66   ModelTypeSet encrypted_types;
67   bool passphrase_missing = false;
68   Cryptographer* cryptographer = dir->GetCryptographer(trans);
69   if (cryptographer) {
70     encrypted_types = dir->GetNigoriHandler()->GetEncryptedTypes(trans);
71     passphrase_missing = cryptographer->has_pending_keys();
72   };
73 
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,
79                        ModelTypeSet(type),
80                        encrypted_types,
81                        passphrase_missing,
82                        all_unsynced_handles,
83                        &ready_unsynced_set);
84 
85   OrderCommitIds(trans, max_entries, ready_unsynced_set, out);
86 
87   for (size_t i = 0; i < out->size(); i++) {
88     DVLOG(1) << "Debug commit batch result:" << (*out)[i];
89   }
90 }
91 
92 namespace {
93 
IsEntryInConflict(const syncable::Entry & entry)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 "
102              << entry;
103     return true;
104   }
105   return false;
106 }
107 
108 // Return true if this entry has any attachments that haven't yet been uploaded
109 // to the server.
HasAttachmentNotOnServer(const syncable::Entry & entry)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()) {
114       return true;
115     }
116   }
117   return false;
118 }
119 
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
124 //    encrypted).
125 // 3. It's type is currently throttled.
126 // 4. It's a delete but has not been committed.
IsEntryReadyForCommit(ModelTypeSet requested_types,ModelTypeSet encrypted_types,bool passphrase_missing,const syncable::Entry & entry)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))
133     return false;
134 
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
138   // (ex: sync_tabs).
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 "
146              << entry;
147     return false;
148   }
149 
150   // Ignore it if it's not in our set of requested types.
151   if (!requested_types.Has(type))
152     return false;
153 
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;
159     return false;
160   }
161 
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;
168     return false;
169   }
170 
171   if (entry.IsRoot()) {
172     NOTREACHED() << "Permanent item became unsynced " << entry;
173     return false;
174   }
175 
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.
180     return false;
181   }
182 
183   DVLOG(2) << "Entry is ready for commit: " << entry;
184   return true;
185 }
186 
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.
FilterUnreadyEntries(syncable::BaseTransaction * trans,ModelTypeSet requested_types,ModelTypeSet encrypted_types,bool passphrase_missing,const syncable::Directory::Metahandles & unsynced_handles,std::set<int64> * ready_unsynced_set)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
203     // (bug 356273).
204     if (IsEntryReadyForCommit(requested_types,
205                               encrypted_types,
206                               passphrase_missing,
207                               entry)) {
208       ready_unsynced_set->insert(*iter);
209     }
210   }
211 }
212 
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.
216 class Traversal {
217  public:
218   Traversal(
219     syncable::BaseTransaction* trans,
220     int64 max_entries,
221     syncable::Directory::Metahandles* out);
222   ~Traversal();
223 
224   // First step of traversal building.  Adds non-deleted items in order.
225   void AddCreatesAndMoves(const std::set<int64>& ready_unsynced_set);
226 
227   // Second step of traverals building.  Appends deleted items.
228   void AddDeletes(const std::set<int64>& ready_unsynced_set);
229 
230  private:
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;
237 
238   void TryAddItem(const std::set<int64>& ready_unsynced_set,
239                   const syncable::Entry& item,
240                   syncable::Directory::Metahandles* result) const;
241 
242   void AddItemThenPredecessors(
243       const std::set<int64>& ready_unsynced_set,
244       const syncable::Entry& item,
245       syncable::Directory::Metahandles* result) const;
246 
247   void AddPredecessorsThenItem(
248       const std::set<int64>& ready_unsynced_set,
249       const syncable::Entry& item,
250       syncable::Directory::Metahandles* result) const;
251 
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;
256 
257   // Returns true if we've collected enough items.
258   bool IsFull() const;
259 
260   // Returns true if the specified handle is already in the traversal.
261   bool HaveItem(int64 handle) const;
262 
263   // Adds the specified handles to the traversal.
264   void AppendManyToTraversal(const syncable::Directory::Metahandles& handles);
265 
266   // Adds the specifed handle to the traversal.
267   void AppendToTraversal(int64 handle);
268 
269   syncable::Directory::Metahandles* out_;
270   std::set<int64> added_handles_;
271   const size_t max_entries_;
272   syncable::BaseTransaction* trans_;
273 
274   DISALLOW_COPY_AND_ASSIGN(Traversal);
275 };
276 
Traversal(syncable::BaseTransaction * trans,int64 max_entries,syncable::Directory::Metahandles * out)277 Traversal::Traversal(
278     syncable::BaseTransaction* trans,
279     int64 max_entries,
280     syncable::Directory::Metahandles* out)
281   : out_(out),
282     max_entries_(max_entries),
283     trans_(trans) { }
284 
~Traversal()285 Traversal::~Traversal() {}
286 
AddUncommittedParentsAndTheirPredecessors(const std::set<int64> & ready_unsynced_set,const syncable::Entry & item,syncable::Directory::Metahandles * result) const287 bool Traversal::AddUncommittedParentsAndTheirPredecessors(
288     const std::set<int64>& ready_unsynced_set,
289     const syncable::Entry& item,
290     syncable::Directory::Metahandles* result) const {
291   syncable::Directory::Metahandles dependencies;
292   syncable::Id parent_id = item.GetParentId();
293 
294   // Climb the tree adding entries leaf -> root.
295   while (!parent_id.ServerKnows()) {
296     syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id);
297     CHECK(parent.good()) << "Bad user-only parent in item path.";
298     int64 handle = parent.GetMetahandle();
299     if (HaveItem(handle)) {
300       // We've already added this parent (and therefore all of its parents).
301       // We can return early.
302       break;
303     }
304     if (IsEntryInConflict(parent)) {
305       // We ignore all entries that are children of a conflicing item.  Return
306       // false immediately to forget the traversal we've built up so far.
307       DVLOG(1) << "Parent was in conflict, omitting " << item;
308       return false;
309     }
310     AddItemThenPredecessors(ready_unsynced_set,
311                             parent,
312                             &dependencies);
313     parent_id = parent.GetParentId();
314   }
315 
316   // Reverse what we added to get the correct order.
317   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
318   return true;
319 }
320 
321 // Adds the given item to the list if it is unsynced and ready for commit.
TryAddItem(const std::set<int64> & ready_unsynced_set,const syncable::Entry & item,syncable::Directory::Metahandles * result) const322 void Traversal::TryAddItem(const std::set<int64>& ready_unsynced_set,
323                            const syncable::Entry& item,
324                            syncable::Directory::Metahandles* result) const {
325   DCHECK(item.GetIsUnsynced());
326   int64 item_handle = item.GetMetahandle();
327   if (ready_unsynced_set.count(item_handle) != 0) {
328     result->push_back(item_handle);
329   }
330 }
331 
332 // Adds the given item, and all its unsynced predecessors.  The traversal will
333 // be cut short if any item along the traversal is not IS_UNSYNCED, or if we
334 // detect that this area of the tree has already been traversed.  Items that are
335 // not 'ready' for commit (see IsEntryReadyForCommit()) will not be added to the
336 // list, though they will not stop the traversal.
AddItemThenPredecessors(const std::set<int64> & ready_unsynced_set,const syncable::Entry & item,syncable::Directory::Metahandles * result) const337 void Traversal::AddItemThenPredecessors(
338     const std::set<int64>& ready_unsynced_set,
339     const syncable::Entry& item,
340     syncable::Directory::Metahandles* result) const {
341   int64 item_handle = item.GetMetahandle();
342   if (HaveItem(item_handle)) {
343     // We've already added this item to the commit set, and so must have
344     // already added the predecessors as well.
345     return;
346   }
347   TryAddItem(ready_unsynced_set, item, result);
348   if (item.GetIsDel())
349     return;  // Deleted items have no predecessors.
350 
351   syncable::Id prev_id = item.GetPredecessorId();
352   while (!prev_id.IsRoot()) {
353     syncable::Entry prev(trans_, syncable::GET_BY_ID, prev_id);
354     CHECK(prev.good()) << "Bad id when walking predecessors.";
355     if (!prev.GetIsUnsynced()) {
356       // We're interested in "runs" of unsynced items.  This item breaks
357       // the streak, so we stop traversing.
358       return;
359     }
360     int64 handle = prev.GetMetahandle();
361     if (HaveItem(handle)) {
362       // We've already added this item to the commit set, and so must have
363       // already added the predecessors as well.
364       return;
365     }
366     TryAddItem(ready_unsynced_set, prev, result);
367     prev_id = prev.GetPredecessorId();
368   }
369 }
370 
371 // Same as AddItemThenPredecessor, but the traversal order will be reversed.
AddPredecessorsThenItem(const std::set<int64> & ready_unsynced_set,const syncable::Entry & item,syncable::Directory::Metahandles * result) const372 void Traversal::AddPredecessorsThenItem(
373     const std::set<int64>& ready_unsynced_set,
374     const syncable::Entry& item,
375     syncable::Directory::Metahandles* result) const {
376   syncable::Directory::Metahandles dependencies;
377   AddItemThenPredecessors(ready_unsynced_set, item, &dependencies);
378 
379   // Reverse what we added to get the correct order.
380   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
381 }
382 
383 // Traverses the tree from bottom to top, adding the deleted parents of the
384 // given |item|.  Stops traversing if it encounters a non-deleted node, or
385 // a node that was already listed in the |traversed| list.  Returns an error
386 // (false) if a node along the traversal is in a conflict state.
387 //
388 // The result list is reversed before it is returned, so the resulting
389 // traversal is in top to bottom order.  Also note that this function appends
390 // to the result list without clearing it.
AddDeletedParents(const std::set<int64> & ready_unsynced_set,const syncable::Entry & item,const syncable::Directory::Metahandles & traversed,syncable::Directory::Metahandles * result) const391 bool Traversal::AddDeletedParents(
392     const std::set<int64>& ready_unsynced_set,
393     const syncable::Entry& item,
394     const syncable::Directory::Metahandles& traversed,
395     syncable::Directory::Metahandles* result) const {
396   syncable::Directory::Metahandles dependencies;
397   syncable::Id parent_id = item.GetParentId();
398 
399   // Climb the tree adding entries leaf -> root.
400   while (!parent_id.IsRoot()) {
401     syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id);
402 
403     if (!parent.good()) {
404       // This is valid because the parent could have gone away a long time ago
405       //
406       // Consider the case where a folder is server-unknown and locally
407       // deleted, and has a child that is server-known, deleted, and unsynced.
408       // The parent could be dropped from memory at any time, but its child
409       // needs to be committed first.
410       break;
411     }
412     int64 handle = parent.GetMetahandle();
413     if (!parent.GetIsUnsynced()) {
414       // In some rare cases, our parent can be both deleted and unsynced.
415       // (ie. the server-unknown parent case).
416       break;
417     }
418     if (!parent.GetIsDel()) {
419       // We're not intersted in non-deleted parents.
420       break;
421     }
422     if (std::find(traversed.begin(), traversed.end(), handle) !=
423         traversed.end()) {
424       // We've already added this parent (and therefore all of its parents).
425       // We can return early.
426       break;
427     }
428     if (IsEntryInConflict(parent)) {
429       // We ignore all entries that are children of a conflicing item.  Return
430       // false immediately to forget the traversal we've built up so far.
431       DVLOG(1) << "Parent was in conflict, omitting " << item;
432       return false;
433     }
434     TryAddItem(ready_unsynced_set, parent, &dependencies);
435     parent_id = parent.GetParentId();
436   }
437 
438   // Reverse what we added to get the correct order.
439   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
440   return true;
441 }
442 
IsFull() const443 bool Traversal::IsFull() const {
444   return out_->size() >= max_entries_;
445 }
446 
HaveItem(int64 handle) const447 bool Traversal::HaveItem(int64 handle) const {
448   return added_handles_.find(handle) != added_handles_.end();
449 }
450 
AppendManyToTraversal(const syncable::Directory::Metahandles & handles)451 void Traversal::AppendManyToTraversal(
452     const syncable::Directory::Metahandles& handles) {
453   out_->insert(out_->end(), handles.begin(), handles.end());
454   added_handles_.insert(handles.begin(), handles.end());
455 }
456 
AppendToTraversal(int64 metahandle)457 void Traversal::AppendToTraversal(int64 metahandle) {
458   out_->push_back(metahandle);
459   added_handles_.insert(metahandle);
460 }
461 
AddCreatesAndMoves(const std::set<int64> & ready_unsynced_set)462 void Traversal::AddCreatesAndMoves(
463     const std::set<int64>& ready_unsynced_set) {
464   // Add moves and creates, and prepend their uncommitted parents.
465   for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
466        !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
467     int64 metahandle = *iter;
468     if (HaveItem(metahandle))
469       continue;
470 
471     syncable::Entry entry(trans_,
472                           syncable::GET_BY_HANDLE,
473                           metahandle);
474     if (!entry.GetIsDel()) {
475       // We only commit an item + its dependencies if it and all its
476       // dependencies are not in conflict.
477       syncable::Directory::Metahandles item_dependencies;
478       if (AddUncommittedParentsAndTheirPredecessors(
479               ready_unsynced_set,
480               entry,
481               &item_dependencies)) {
482         AddPredecessorsThenItem(ready_unsynced_set,
483                                 entry,
484                                 &item_dependencies);
485         AppendManyToTraversal(item_dependencies);
486       }
487     }
488   }
489 
490   // It's possible that we overcommitted while trying to expand dependent
491   // items.  If so, truncate the set down to the allowed size.
492   if (out_->size() > max_entries_)
493     out_->resize(max_entries_);
494 }
495 
AddDeletes(const std::set<int64> & ready_unsynced_set)496 void Traversal::AddDeletes(const std::set<int64>& ready_unsynced_set) {
497   syncable::Directory::Metahandles deletion_list;
498 
499   for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
500        !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
501     int64 metahandle = *iter;
502 
503     if (HaveItem(metahandle))
504       continue;
505 
506     if (std::find(deletion_list.begin(), deletion_list.end(), metahandle) !=
507         deletion_list.end()) {
508       continue;
509     }
510 
511     syncable::Entry entry(trans_, syncable::GET_BY_HANDLE,
512                           metahandle);
513 
514     if (entry.GetIsDel()) {
515       syncable::Directory::Metahandles parents;
516       if (AddDeletedParents(
517               ready_unsynced_set, entry, deletion_list, &parents)) {
518         // Append parents and chilren in top to bottom order.
519         deletion_list.insert(deletion_list.end(),
520                              parents.begin(),
521                              parents.end());
522         deletion_list.push_back(metahandle);
523       }
524     }
525 
526     if (deletion_list.size() + out_->size() > max_entries_)
527       break;
528   }
529 
530   // We've been gathering deletions in top to bottom order.  Now we reverse the
531   // order as we prepare the list.
532   std::reverse(deletion_list.begin(), deletion_list.end());
533   AppendManyToTraversal(deletion_list);
534 
535   // It's possible that we overcommitted while trying to expand dependent
536   // items.  If so, truncate the set down to the allowed size.
537   if (out_->size() > max_entries_)
538     out_->resize(max_entries_);
539 }
540 
OrderCommitIds(syncable::BaseTransaction * trans,size_t max_entries,const std::set<int64> & ready_unsynced_set,syncable::Directory::Metahandles * out)541 void OrderCommitIds(
542     syncable::BaseTransaction* trans,
543     size_t max_entries,
544     const std::set<int64>& ready_unsynced_set,
545     syncable::Directory::Metahandles* out) {
546   // Commits follow these rules:
547   // 1. Moves or creates are preceded by needed folder creates, from
548   //    root to leaf.  For folders whose contents are ordered, moves
549   //    and creates appear in order.
550   // 2. Moves/Creates before deletes.
551   // 3. Deletes, collapsed.
552   // We commit deleted moves under deleted items as moves when collapsing
553   // delete trees.
554 
555   Traversal traversal(trans, max_entries, out);
556 
557   // Add moves and creates, and prepend their uncommitted parents.
558   traversal.AddCreatesAndMoves(ready_unsynced_set);
559 
560   // Add all deletes.
561   traversal.AddDeletes(ready_unsynced_set);
562 }
563 
564 }  // namespace
565 
566 }  // namespace syncer
567