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