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 // Return true if this entry has any attachments that haven't yet been uploaded
    109 // to the server.
    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.
    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.
    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 
    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 
    285 Traversal::~Traversal() {}
    286 
    287 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.
    322 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.
    337 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.
    372 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.
    391 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 
    443 bool Traversal::IsFull() const {
    444   return out_->size() >= max_entries_;
    445 }
    446 
    447 bool Traversal::HaveItem(int64 handle) const {
    448   return added_handles_.find(handle) != added_handles_.end();
    449 }
    450 
    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 
    457 void Traversal::AppendToTraversal(int64 metahandle) {
    458   out_->push_back(metahandle);
    459   added_handles_.insert(metahandle);
    460 }
    461 
    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 
    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 
    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