Home | History | Annotate | Download | only in engine
      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 
     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 
     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 // An entry is not considered ready for commit if any are true:
    109 // 1. It's in conflict.
    110 // 2. It requires encryption (either the type is encrypted but a passphrase
    111 //    is missing from the cryptographer, or the entry itself wasn't properly
    112 //    encrypted).
    113 // 3. It's type is currently throttled.
    114 // 4. It's a delete but has not been committed.
    115 bool IsEntryReadyForCommit(ModelTypeSet requested_types,
    116                            ModelTypeSet encrypted_types,
    117                            bool passphrase_missing,
    118                            const syncable::Entry& entry) {
    119   DCHECK(entry.GetIsUnsynced());
    120   if (IsEntryInConflict(entry))
    121     return false;
    122 
    123   const ModelType type = entry.GetModelType();
    124   // We special case the nigori node because even though it is considered an
    125   // "encrypted type", not all nigori node changes require valid encryption
    126   // (ex: sync_tabs).
    127   if ((type != NIGORI) && encrypted_types.Has(type) &&
    128       (passphrase_missing ||
    129        syncable::EntryNeedsEncryption(encrypted_types, entry))) {
    130     // This entry requires encryption but is not properly encrypted (possibly
    131     // due to the cryptographer not being initialized or the user hasn't
    132     // provided the most recent passphrase).
    133     DVLOG(1) << "Excluding entry from commit due to lack of encryption "
    134              << entry;
    135     return false;
    136   }
    137 
    138   // Ignore it if it's not in our set of requested types.
    139   if (!requested_types.Has(type))
    140     return false;
    141 
    142   if (entry.GetIsDel() && !entry.GetId().ServerKnows()) {
    143     // New clients (following the resolution of crbug.com/125381) should not
    144     // create such items.  Old clients may have left some in the database
    145     // (crbug.com/132905), but we should now be cleaning them on startup.
    146     NOTREACHED() << "Found deleted and unsynced local item: " << entry;
    147     return false;
    148   }
    149 
    150   // Extra validity checks.
    151   syncable::Id id = entry.GetId();
    152   if (id == entry.GetParentId()) {
    153     CHECK(id.IsRoot()) << "Non-root item is self parenting." << entry;
    154     // If the root becomes unsynced it can cause us problems.
    155     NOTREACHED() << "Root item became unsynced " << entry;
    156     return false;
    157   }
    158 
    159   if (entry.IsRoot()) {
    160     NOTREACHED() << "Permanent item became unsynced " << entry;
    161     return false;
    162   }
    163 
    164   DVLOG(2) << "Entry is ready for commit: " << entry;
    165   return true;
    166 }
    167 
    168 // Filters |unsynced_handles| to remove all entries that do not belong to the
    169 // specified |requested_types|, or are not eligible for a commit at this time.
    170 void FilterUnreadyEntries(
    171     syncable::BaseTransaction* trans,
    172     ModelTypeSet requested_types,
    173     ModelTypeSet encrypted_types,
    174     bool passphrase_missing,
    175     const syncable::Directory::Metahandles& unsynced_handles,
    176     std::set<int64>* ready_unsynced_set) {
    177   for (syncable::Directory::Metahandles::const_iterator iter =
    178        unsynced_handles.begin(); iter != unsynced_handles.end(); ++iter) {
    179     syncable::Entry entry(trans, syncable::GET_BY_HANDLE, *iter);
    180     if (IsEntryReadyForCommit(requested_types,
    181                               encrypted_types,
    182                               passphrase_missing,
    183                               entry)) {
    184       ready_unsynced_set->insert(*iter);
    185     }
    186   }
    187 }
    188 
    189 // This class helps to implement OrderCommitIds().  Its members track the
    190 // progress of a traversal while its methods extend it.  It can return early if
    191 // the traversal reaches the desired size before the full traversal is complete.
    192 class Traversal {
    193  public:
    194   Traversal(
    195     syncable::BaseTransaction* trans,
    196     int64 max_entries,
    197     syncable::Directory::Metahandles* out);
    198   ~Traversal();
    199 
    200   // First step of traversal building.  Adds non-deleted items in order.
    201   void AddCreatesAndMoves(const std::set<int64>& ready_unsynced_set);
    202 
    203   // Second step of traverals building.  Appends deleted items.
    204   void AddDeletes(const std::set<int64>& ready_unsynced_set);
    205 
    206  private:
    207   // The following functions do not modify the traversal directly.  They return
    208   // their results in the |result| vector instead.
    209   bool AddUncommittedParentsAndTheirPredecessors(
    210       const std::set<int64>& ready_unsynced_set,
    211       const syncable::Entry& item,
    212       syncable::Directory::Metahandles* result) const;
    213 
    214   void TryAddItem(const std::set<int64>& ready_unsynced_set,
    215                   const syncable::Entry& item,
    216                   syncable::Directory::Metahandles* result) const;
    217 
    218   void AddItemThenPredecessors(
    219       const std::set<int64>& ready_unsynced_set,
    220       const syncable::Entry& item,
    221       syncable::Directory::Metahandles* result) const;
    222 
    223   void AddPredecessorsThenItem(
    224       const std::set<int64>& ready_unsynced_set,
    225       const syncable::Entry& item,
    226       syncable::Directory::Metahandles* result) const;
    227 
    228   // Returns true if we've collected enough items.
    229   bool IsFull() const;
    230 
    231   // Returns true if the specified handle is already in the traversal.
    232   bool HaveItem(int64 handle) const;
    233 
    234   // Adds the specified handles to the traversal.
    235   void AppendManyToTraversal(const syncable::Directory::Metahandles& handles);
    236 
    237   // Adds the specifed handle to the traversal.
    238   void AppendToTraversal(int64 handle);
    239 
    240   syncable::Directory::Metahandles* out_;
    241   std::set<int64> added_handles_;
    242   const size_t max_entries_;
    243   syncable::BaseTransaction* trans_;
    244 
    245   DISALLOW_COPY_AND_ASSIGN(Traversal);
    246 };
    247 
    248 Traversal::Traversal(
    249     syncable::BaseTransaction* trans,
    250     int64 max_entries,
    251     syncable::Directory::Metahandles* out)
    252   : out_(out),
    253     max_entries_(max_entries),
    254     trans_(trans) { }
    255 
    256 Traversal::~Traversal() {}
    257 
    258 bool Traversal::AddUncommittedParentsAndTheirPredecessors(
    259     const std::set<int64>& ready_unsynced_set,
    260     const syncable::Entry& item,
    261     syncable::Directory::Metahandles* result) const {
    262   syncable::Directory::Metahandles dependencies;
    263   syncable::Id parent_id = item.GetParentId();
    264 
    265   // Climb the tree adding entries leaf -> root.
    266   while (!parent_id.ServerKnows()) {
    267     syncable::Entry parent(trans_, syncable::GET_BY_ID, parent_id);
    268     CHECK(parent.good()) << "Bad user-only parent in item path.";
    269     int64 handle = parent.GetMetahandle();
    270     if (HaveItem(handle)) {
    271       // We've already added this parent (and therefore all of its parents).
    272       // We can return early.
    273       break;
    274     }
    275     if (IsEntryInConflict(parent)) {
    276       // We ignore all entries that are children of a conflicing item.  Return
    277       // false immediately to forget the traversal we've built up so far.
    278       DVLOG(1) << "Parent was in conflict, omitting " << item;
    279       return false;
    280     }
    281     AddItemThenPredecessors(ready_unsynced_set,
    282                             parent,
    283                             &dependencies);
    284     parent_id = parent.GetParentId();
    285   }
    286 
    287   // Reverse what we added to get the correct order.
    288   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
    289   return true;
    290 }
    291 
    292 // Adds the given item to the list if it is unsynced and ready for commit.
    293 void Traversal::TryAddItem(const std::set<int64>& ready_unsynced_set,
    294                            const syncable::Entry& item,
    295                            syncable::Directory::Metahandles* result) const {
    296   DCHECK(item.GetIsUnsynced());
    297   int64 item_handle = item.GetMetahandle();
    298   if (ready_unsynced_set.count(item_handle) != 0) {
    299     result->push_back(item_handle);
    300   }
    301 }
    302 
    303 // Adds the given item, and all its unsynced predecessors.  The traversal will
    304 // be cut short if any item along the traversal is not IS_UNSYNCED, or if we
    305 // detect that this area of the tree has already been traversed.  Items that are
    306 // not 'ready' for commit (see IsEntryReadyForCommit()) will not be added to the
    307 // list, though they will not stop the traversal.
    308 void Traversal::AddItemThenPredecessors(
    309     const std::set<int64>& ready_unsynced_set,
    310     const syncable::Entry& item,
    311     syncable::Directory::Metahandles* result) const {
    312   int64 item_handle = item.GetMetahandle();
    313   if (HaveItem(item_handle)) {
    314     // We've already added this item to the commit set, and so must have
    315     // already added the predecessors as well.
    316     return;
    317   }
    318   TryAddItem(ready_unsynced_set, item, result);
    319   if (item.GetIsDel())
    320     return;  // Deleted items have no predecessors.
    321 
    322   syncable::Id prev_id = item.GetPredecessorId();
    323   while (!prev_id.IsRoot()) {
    324     syncable::Entry prev(trans_, syncable::GET_BY_ID, prev_id);
    325     CHECK(prev.good()) << "Bad id when walking predecessors.";
    326     if (!prev.GetIsUnsynced()) {
    327       // We're interested in "runs" of unsynced items.  This item breaks
    328       // the streak, so we stop traversing.
    329       return;
    330     }
    331     int64 handle = prev.GetMetahandle();
    332     if (HaveItem(handle)) {
    333       // We've already added this item to the commit set, and so must have
    334       // already added the predecessors as well.
    335       return;
    336     }
    337     TryAddItem(ready_unsynced_set, prev, result);
    338     prev_id = prev.GetPredecessorId();
    339   }
    340 }
    341 
    342 // Same as AddItemThenPredecessor, but the traversal order will be reversed.
    343 void Traversal::AddPredecessorsThenItem(
    344     const std::set<int64>& ready_unsynced_set,
    345     const syncable::Entry& item,
    346     syncable::Directory::Metahandles* result) const {
    347   syncable::Directory::Metahandles dependencies;
    348   AddItemThenPredecessors(ready_unsynced_set, item, &dependencies);
    349 
    350   // Reverse what we added to get the correct order.
    351   result->insert(result->end(), dependencies.rbegin(), dependencies.rend());
    352 }
    353 
    354 bool Traversal::IsFull() const {
    355   return out_->size() >= max_entries_;
    356 }
    357 
    358 bool Traversal::HaveItem(int64 handle) const {
    359   return added_handles_.find(handle) != added_handles_.end();
    360 }
    361 
    362 void Traversal::AppendManyToTraversal(
    363     const syncable::Directory::Metahandles& handles) {
    364   out_->insert(out_->end(), handles.begin(), handles.end());
    365   added_handles_.insert(handles.begin(), handles.end());
    366 }
    367 
    368 void Traversal::AppendToTraversal(int64 metahandle) {
    369   out_->push_back(metahandle);
    370   added_handles_.insert(metahandle);
    371 }
    372 
    373 void Traversal::AddCreatesAndMoves(
    374     const std::set<int64>& ready_unsynced_set) {
    375   // Add moves and creates, and prepend their uncommitted parents.
    376   for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
    377        !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
    378     int64 metahandle = *iter;
    379     if (HaveItem(metahandle))
    380       continue;
    381 
    382     syncable::Entry entry(trans_,
    383                           syncable::GET_BY_HANDLE,
    384                           metahandle);
    385     if (!entry.GetIsDel()) {
    386       // We only commit an item + its dependencies if it and all its
    387       // dependencies are not in conflict.
    388       syncable::Directory::Metahandles item_dependencies;
    389       if (AddUncommittedParentsAndTheirPredecessors(
    390               ready_unsynced_set,
    391               entry,
    392               &item_dependencies)) {
    393         AddPredecessorsThenItem(ready_unsynced_set,
    394                                 entry,
    395                                 &item_dependencies);
    396         AppendManyToTraversal(item_dependencies);
    397       }
    398     }
    399   }
    400 
    401   // It's possible that we overcommitted while trying to expand dependent
    402   // items.  If so, truncate the set down to the allowed size.
    403   if (out_->size() > max_entries_)
    404     out_->resize(max_entries_);
    405 }
    406 
    407 void Traversal::AddDeletes(
    408     const std::set<int64>& ready_unsynced_set) {
    409   set<syncable::Id> legal_delete_parents;
    410 
    411   for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
    412        !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
    413     int64 metahandle = *iter;
    414     if (HaveItem(metahandle))
    415       continue;
    416 
    417     syncable::Entry entry(trans_, syncable::GET_BY_HANDLE,
    418                           metahandle);
    419 
    420     if (entry.GetIsDel()) {
    421       syncable::Entry parent(trans_, syncable::GET_BY_ID,
    422                              entry.GetParentId());
    423       // If the parent is deleted and unsynced, then any children of that
    424       // parent don't need to be added to the delete queue.
    425       //
    426       // Note: the parent could be synced if there was an update deleting a
    427       // folder when we had a deleted all items in it.
    428       // We may get more updates, or we may want to delete the entry.
    429       if (parent.good() && parent.GetIsDel() && parent.GetIsUnsynced()) {
    430         // However, if an entry is moved, these rules can apply differently.
    431         //
    432         // If the entry was moved, then the destination parent was deleted,
    433         // then we'll miss it in the roll up. We have to add it in manually.
    434         // TODO(chron): Unit test for move / delete cases:
    435         // Case 1: Locally moved, then parent deleted
    436         // Case 2: Server moved, then locally issue recursive delete.
    437         if (entry.GetId().ServerKnows() &&
    438             entry.GetParentId() != entry.GetServerParentId()) {
    439           DVLOG(1) << "Inserting moved and deleted entry, will be missed by "
    440                    << "delete roll." << entry.GetId();
    441 
    442           AppendToTraversal(metahandle);
    443         }
    444 
    445         // Skip this entry since it's a child of a parent that will be
    446         // deleted. The server will unroll the delete and delete the
    447         // child as well.
    448         continue;
    449       }
    450 
    451       legal_delete_parents.insert(entry.GetParentId());
    452     }
    453   }
    454 
    455   // We could store all the potential entries with a particular parent during
    456   // the above scan, but instead we rescan here. This is less efficient, but
    457   // we're dropping memory alloc/dealloc in favor of linear scans of recently
    458   // examined entries.
    459   //
    460   // Scan through the UnsyncedMetaHandles again. If we have a deleted
    461   // entry, then check if the parent is in legal_delete_parents.
    462   //
    463   // Parent being in legal_delete_parents means for the child:
    464   //   a recursive delete is not currently happening (no recent deletes in same
    465   //     folder)
    466   //   parent did expect at least one old deleted child
    467   //   parent was not deleted
    468   for (std::set<int64>::const_iterator iter = ready_unsynced_set.begin();
    469        !IsFull() && iter != ready_unsynced_set.end(); ++iter) {
    470     int64 metahandle = *iter;
    471     if (HaveItem(metahandle))
    472       continue;
    473     syncable::Entry entry(trans_, syncable::GET_BY_HANDLE, metahandle);
    474     if (entry.GetIsDel()) {
    475       syncable::Id parent_id = entry.GetParentId();
    476       if (legal_delete_parents.count(parent_id)) {
    477         AppendToTraversal(metahandle);
    478       }
    479     }
    480   }
    481 }
    482 
    483 void OrderCommitIds(
    484     syncable::BaseTransaction* trans,
    485     size_t max_entries,
    486     const std::set<int64>& ready_unsynced_set,
    487     syncable::Directory::Metahandles* out) {
    488   // Commits follow these rules:
    489   // 1. Moves or creates are preceded by needed folder creates, from
    490   //    root to leaf.  For folders whose contents are ordered, moves
    491   //    and creates appear in order.
    492   // 2. Moves/Creates before deletes.
    493   // 3. Deletes, collapsed.
    494   // We commit deleted moves under deleted items as moves when collapsing
    495   // delete trees.
    496 
    497   Traversal traversal(trans, max_entries, out);
    498 
    499   // Add moves and creates, and prepend their uncommitted parents.
    500   traversal.AddCreatesAndMoves(ready_unsynced_set);
    501 
    502   // Add all deletes.
    503   traversal.AddDeletes(ready_unsynced_set);
    504 }
    505 
    506 }  // namespace
    507 
    508 }  // namespace syncer
    509