Chromium Code Reviews| Index: chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc |
| =================================================================== |
| --- chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc (revision 0) |
| +++ chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc (revision 0) |
| @@ -0,0 +1,439 @@ |
| +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "chrome/browser/sync/engine/build_and_process_conflict_sets_command.h" |
| + |
| +#include <string> |
| +#include <sstream> |
| +#include <vector> |
| + |
| +#include "base/basictypes.h" |
| +#include "base/format_macros.h" |
| +#include "base/rand_util.h" |
| +#include "chrome/browser/sync/engine/conflict_resolution_view.h" |
| +#include "chrome/browser/sync/engine/syncer_util.h" |
| +#include "chrome/browser/sync/engine/update_applicator.h" |
| +#include "chrome/browser/sync/syncable/directory_manager.h" |
| + |
| +namespace browser_sync { |
| + |
| +using std::set; |
| +using std::string; |
| +using std::vector; |
| + |
| +BuildAndProcessConflictSetsCommand::BuildAndProcessConflictSetsCommand() {} |
| +BuildAndProcessConflictSetsCommand::~BuildAndProcessConflictSetsCommand() {} |
| + |
| +void BuildAndProcessConflictSetsCommand::ModelChangingExecuteImpl( |
| + SyncerSession *session) { |
|
idana
2009/09/10 05:44:37
nit: "SyncerSession *session" -> "SyncerSession* s
|
| + session->set_conflict_sets_built(BuildAndProcessConflictSets(session)); |
| +} |
| + |
| +bool BuildAndProcessConflictSetsCommand::BuildAndProcessConflictSets( |
| + SyncerSession *session) { |
|
idana
2009/09/10 05:44:37
nit: "SyncerSession *session" -> "SyncerSession* s
|
| + syncable::ScopedDirLookup dir(session->dirman(), session->account_name()); |
| + if (!dir.good()) |
| + return false; |
| + bool had_single_direction_sets = false; |
| + { // scope for transaction |
| + syncable::WriteTransaction trans(dir, syncable::SYNCER, __FILE__, __LINE__); |
| + ConflictResolutionView conflict_view(session); |
| + BuildConflictSets(&trans, &conflict_view); |
| + had_single_direction_sets = |
| + ProcessSingleDirectionConflictSets(&trans, session); |
| + // we applied some updates transactionally, lets try syncing again. |
| + if (had_single_direction_sets) |
| + return true; |
| + } |
| + return false; |
| +} |
| + |
| +bool BuildAndProcessConflictSetsCommand::ProcessSingleDirectionConflictSets( |
| + syncable::WriteTransaction* trans, SyncerSession* const session) { |
| + bool rv = false; |
| + ConflictResolutionView conflict_view(session); |
| + set<ConflictSet*>::const_iterator all_sets_iterator; |
| + for(all_sets_iterator = conflict_view.ConflictSetsBegin(); |
| + all_sets_iterator != conflict_view.ConflictSetsEnd() ; ) { |
| + const ConflictSet* conflict_set = *all_sets_iterator; |
| + CHECK(conflict_set->size() >= 2); |
| + // We scan the set to see if it consists of changes of only one type. |
| + ConflictSet::const_iterator i; |
| + int unsynced_count = 0, unapplied_count = 0; |
| + for (i = conflict_set->begin(); i != conflict_set->end(); ++i) { |
| + syncable::Entry entry(trans, syncable::GET_BY_ID, *i); |
| + CHECK(entry.good()); |
| + if (entry.Get(syncable::IS_UNSYNCED)) |
| + unsynced_count++; |
| + if (entry.Get(syncable::IS_UNAPPLIED_UPDATE)) |
| + unapplied_count++; |
| + } |
| + if (conflict_set->size() == unsynced_count && 0 == unapplied_count) { |
| + LOG(INFO) << "Skipped transactional commit attempt."; |
| + } else if (conflict_set->size() == unapplied_count && |
| + 0 == unsynced_count && |
| + ApplyUpdatesTransactionally(trans, conflict_set, session)) { |
| + rv = true; |
| + } |
| + ++all_sets_iterator; |
| + } |
| + return rv; |
| +} |
| + |
| +namespace { |
| + |
| +void StoreLocalDataForUpdateRollback(syncable::Entry* entry, |
| + syncable::EntryKernel* backup) { |
| + CHECK(!entry->Get(syncable::IS_UNSYNCED)) << " Storing Rollback data for " |
| + "entry that's unsynced." << *entry ; |
| + CHECK(entry->Get(syncable::IS_UNAPPLIED_UPDATE)) << " Storing Rollback data " |
| + "for entry that's not an unapplied update." << *entry ; |
| + *backup = entry->GetKernelCopy(); |
| +} |
| + |
| +class UniqueNameGenerator { |
| + public: |
| + void Initialize() { |
| + // To avoid name collisions we prefix the names with hex data derived from |
| + // 64 bits of randomness. |
| + int64 name_prefix = static_cast<int64>(base::RandUint64()); |
| + name_stem_ = StringPrintf("%0" PRId64 "x.", name_prefix); |
| + } |
| + string StringNameForEntry(const syncable::Entry& entry) { |
| + CHECK(!name_stem_.empty()); |
| + std::stringstream rv; |
| + rv << name_stem_ << entry.Get(syncable::ID); |
| + return rv.str(); |
| + } |
| + PathString PathStringNameForEntry(const syncable::Entry& entry) { |
| + string name = StringNameForEntry(entry); |
| + return PathString(name.begin(), name.end()); |
| + } |
| + |
| + private: |
| + string name_stem_; |
| +}; |
| + |
| +bool RollbackEntry(syncable::WriteTransaction* trans, |
| + syncable::EntryKernel* backup) { |
| + syncable::MutableEntry entry(trans, syncable::GET_BY_HANDLE, |
| + backup->ref(syncable::META_HANDLE)); |
| + CHECK(entry.good()); |
| + bool was_del = entry.Get(syncable::IS_DEL); |
| + |
| + if (!entry.Put(syncable::IS_DEL, backup->ref(syncable::IS_DEL))) |
| + return false; |
| + syncable::Name name = syncable::Name::FromEntryKernel(backup); |
| + if (!entry.PutParentIdAndName(backup->ref(syncable::PARENT_ID), name)) |
| + return false; |
| + |
| + if (!backup->ref(syncable::IS_DEL)) { |
| + if (!entry.PutPredecessor(backup->ref(syncable::PREV_ID))) |
| + return false; |
| + } |
| + |
| + if (backup->ref(syncable::PREV_ID) != entry.Get(syncable::PREV_ID)) |
| + return false; |
| + |
| + entry.Put(syncable::CTIME, backup->ref(syncable::CTIME)); |
| + entry.Put(syncable::MTIME, backup->ref(syncable::MTIME)); |
| + entry.Put(syncable::BASE_VERSION, backup->ref(syncable::BASE_VERSION)); |
| + entry.Put(syncable::IS_DIR, backup->ref(syncable::IS_DIR)); |
| + entry.Put(syncable::IS_DEL, backup->ref(syncable::IS_DEL)); |
| + entry.Put(syncable::ID, backup->ref(syncable::ID)); |
| + entry.Put(syncable::IS_UNAPPLIED_UPDATE, |
| + backup->ref(syncable::IS_UNAPPLIED_UPDATE)); |
| + return true; |
| +} |
| + |
| +class TransactionalUpdateEntryPreparer { |
| + public: |
| + TransactionalUpdateEntryPreparer() { |
| + namegen_.Initialize(); |
| + } |
| + |
| + void PrepareEntries(syncable::WriteTransaction* trans, |
| + const vector<syncable::Id>* ids) { |
| + vector<syncable::Id>::const_iterator it; |
| + for (it = ids->begin(); it != ids->end(); ++it) { |
| + syncable::MutableEntry entry(trans, syncable::GET_BY_ID, *it); |
| + syncable::Name random_name(namegen_.PathStringNameForEntry(entry)); |
| + CHECK(entry.PutParentIdAndName(trans->root_id(), random_name)); |
| + } |
| + } |
| + |
| + private: |
| + UniqueNameGenerator namegen_; |
| + DISALLOW_COPY_AND_ASSIGN(TransactionalUpdateEntryPreparer); |
| +}; |
| + |
| +} // namespace |
| + |
| +bool BuildAndProcessConflictSetsCommand::ApplyUpdatesTransactionally( |
| + syncable::WriteTransaction* trans, |
| + const vector<syncable::Id>* const update_set, |
| + SyncerSession* const session) { |
| + vector<int64> handles; // The handles in the |update_set| order. |
| + vector<syncable::Id> rollback_ids; // Holds the same Ids as update_set, but |
| + // sorted so that runs of adjacent nodes |
| + // appear in order. |
|
idana
2009/09/10 05:44:37
Can you please change this comment paragraph so th
|
| + rollback_ids.reserve(update_set->size()); |
| + syncable::MetahandleSet rollback_ids_inserted_items; // Tracks what's added |
| + // to |rollback_ids|. |
|
idana
2009/09/10 05:44:37
Same as above.
|
| + |
| + vector<syncable::Id>::const_iterator it; |
| + // 1. Build |rollback_ids| in the order required for successful rollback. |
| + // Specifically, for positions to come out right, restoring an item |
| + // requires that its predecessor in the sibling order is properly |
| + // restored first. |
| + // 2. Build |handles|, the list of handles for ApplyUpdates. |
| + for (it = update_set->begin(); it != update_set->end(); ++it) { |
| + syncable::Entry entry(trans, syncable::GET_BY_ID, *it); |
| + SyncerUtil::AddPredecessorsThenItem(trans, &entry, |
| + syncable::IS_UNAPPLIED_UPDATE, &rollback_ids_inserted_items, |
| + &rollback_ids); |
| + handles.push_back(entry.Get(syncable::META_HANDLE)); |
| + } |
| + DCHECK_EQ(rollback_ids.size(), update_set->size()); |
| + DCHECK_EQ(rollback_ids_inserted_items.size(), update_set->size()); |
| + |
| + // 3. Store the information needed to rollback if the transaction fails. |
| + // Do this before modifying anything to keep the next/prev values intact. |
| + vector<syncable::EntryKernel> rollback_data(rollback_ids.size()); |
| + for (size_t i = 0; i < rollback_ids.size(); ++i) { |
| + syncable::Entry entry(trans, syncable::GET_BY_ID, rollback_ids[i]); |
| + StoreLocalDataForUpdateRollback(&entry, &rollback_data[i]); |
| + } |
| + |
| + // 4. Use the preparer to move things to an initial starting state where no |
| + // names collide, and nothing in the set is a child of anything else. If |
| + // we've correctly calculated the set, the server tree is valid and no |
| + // changes have occurred locally we should be able to apply updates from this |
| + // state. |
| + TransactionalUpdateEntryPreparer preparer; |
| + preparer.PrepareEntries(trans, update_set); |
| + |
| + // 5. Use the usual apply updates from the special start state we've just |
| + // prepared. |
| + UpdateApplicator applicator(session, handles.begin(), handles.end()); |
| + while (applicator.AttemptOneApplication(trans)) { |
| + // Keep going till all updates are applied. |
| + } |
| + if (!applicator.AllUpdatesApplied()) { |
| + LOG(ERROR) << "Transactional Apply Failed, Rolling back."; |
| + // We have to move entries into the temp dir again. e.g. if a swap was in a |
| + // set with other failing updates, the swap may have gone through, meaning |
| + // the roll back needs to be transactional. But as we're going to a known |
| + // good state we should always succeed. |
| + preparer.PrepareEntries(trans, update_set); |
| + |
| + // Rollback all entries. |
| + for (size_t i = 0; i < rollback_data.size(); ++i) { |
| + CHECK(RollbackEntry(trans, &rollback_data[i])); |
| + } |
| + return false; // Don't save progress -- we just undid it. |
| + } |
| + applicator.SaveProgressIntoSessionState(); |
| + return true; |
| +} |
| + |
| +void BuildAndProcessConflictSetsCommand::BuildConflictSets( |
| + syncable::BaseTransaction* trans, |
| + ConflictResolutionView* view) { |
| + view->CleanupSets(); |
| + set<syncable::Id>::iterator i = view->CommitConflictsBegin(); |
| + while (i != view->CommitConflictsEnd()) { |
| + syncable::Entry entry(trans, syncable::GET_BY_ID, *i); |
| + CHECK(entry.good()); |
| + if (!entry.Get(syncable::IS_UNSYNCED) && |
| + !entry.Get(syncable::IS_UNAPPLIED_UPDATE)) { |
| + // This can happen very rarely. It means we had a simply conflicting item |
| + // that randomly committed. We drop the entry as it's no longer |
| + // conflicting. |
| + view->EraseCommitConflict(i++); |
| + continue; |
| + } |
| + if (entry.ExistsOnClientBecauseDatabaseNameIsNonEmpty() && |
| + (entry.Get(syncable::IS_DEL) || entry.Get(syncable::SERVER_IS_DEL))) { |
| + // If we're deleted on client or server we can't be in a complex set. |
| + ++i; |
| + continue; |
| + } |
| + bool new_parent = |
| + entry.Get(syncable::PARENT_ID) != entry.Get(syncable::SERVER_PARENT_ID); |
| + bool new_name = 0 != syncable::ComparePathNames(entry.GetSyncNameValue(), |
| + entry.Get(syncable::SERVER_NAME)); |
| + if (new_parent || new_name) |
| + MergeSetsForNameClash(trans, &entry, view); |
| + if (new_parent) |
| + MergeSetsForIntroducedLoops(trans, &entry, view); |
| + MergeSetsForNonEmptyDirectories(trans, &entry, view); |
| + ++i; |
| + } |
| +} |
| + |
| +void BuildAndProcessConflictSetsCommand::MergeSetsForNameClash( |
| + syncable::BaseTransaction* trans, syncable::Entry* entry, |
| + ConflictResolutionView* view) { |
| + PathString server_name = entry->Get(syncable::SERVER_NAME); |
| + // Uncommitted entries have no server name. We trap this because the root |
| + // item has a null name and 0 parentid. |
| + if (server_name.empty()) |
| + return; |
| + syncable::Id conflicting_id = |
| + SyncerUtil::GetNameConflictingItemId( |
| + trans, entry->Get(syncable::SERVER_PARENT_ID), server_name); |
| + if (syncable::kNullId != conflicting_id) |
| + view->MergeSets(entry->Get(syncable::ID), conflicting_id); |
| +} |
| + |
| +void BuildAndProcessConflictSetsCommand::MergeSetsForIntroducedLoops( |
| + syncable::BaseTransaction* trans, syncable::Entry* entry, |
| + ConflictResolutionView* view) { |
| + // This code crawls up from the item in question until it gets to the root |
| + // or itself. If it gets to the root it does nothing. If it finds a loop all |
| + // moved unsynced entries in the list of crawled entries have their sets |
| + // merged with the entry. |
| + // TODO(sync): Build test cases to cover this function when the argument |
| + // list has settled. |
| + syncable::Id parent_id = entry->Get(syncable::SERVER_PARENT_ID); |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); |
| + if (!parent.good()) { |
| + return; |
| + } |
| + // Don't check for loop if the server parent is deleted. |
| + if (parent.Get(syncable::IS_DEL)) |
| + return; |
| + vector<syncable::Id> conflicting_entries; |
| + while (!parent_id.IsRoot()) { |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); |
| + if (!parent.good()) { |
| + LOG(INFO) << "Bad parent in loop check, skipping. Bad parent id: " |
| + << parent_id << " entry: " << *entry; |
| + return; |
| + } |
| + if (parent.Get(syncable::IS_UNSYNCED) && |
| + entry->Get(syncable::PARENT_ID) != |
| + entry->Get(syncable::SERVER_PARENT_ID)) |
| + conflicting_entries.push_back(parent_id); |
| + parent_id = parent.Get(syncable::PARENT_ID); |
| + if (parent_id == entry->Get(syncable::ID)) |
| + break; |
| + } |
| + if (parent_id.IsRoot()) |
| + return; |
| + for (size_t i = 0; i < conflicting_entries.size(); i++) { |
| + view->MergeSets(entry->Get(syncable::ID), conflicting_entries[i]); |
| + } |
| +} |
| + |
| +namespace { |
| + |
| +class ServerDeletedPathChecker { |
| + public: |
| + bool CausingConflict(const syncable::Entry& e, |
| + const syncable::Entry& log_entry) { |
| + CHECK(e.good()) << "Missing parent in path of: " << log_entry; |
| + if (e.Get(syncable::IS_UNAPPLIED_UPDATE) && |
| + e.Get(syncable::SERVER_IS_DEL)) { |
| + CHECK(!e.Get(syncable::IS_DEL)) << " Inconsistency in local tree. " |
| + "syncable::Entry: " << e << " Leaf: " << log_entry; |
| + return true; |
| + } else { |
| + CHECK(!e.Get(syncable::IS_DEL)) << " Deleted entry has children. " |
| + "syncable::Entry: " << e << " Leaf: " << log_entry; |
| + return false; |
| + } |
| + } |
| + // returns 0 if we should stop investigating the path. |
| + syncable::Id GetAndExamineParent(syncable::BaseTransaction* trans, |
| + syncable::Id id, |
| + syncable::Id check_id, |
| + const syncable::Entry& log_entry) { |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, id); |
| + CHECK(parent.good()) << "Tree inconsitency, missing id" << id << " " |
| + << log_entry; |
| + syncable::Id parent_id = parent.Get(syncable::PARENT_ID); |
| + CHECK(parent_id != check_id) << "Loop in dir tree! " |
| + << log_entry << " " << parent; |
| + return parent_id; |
| + } |
| +}; |
| + |
| +class LocallyDeletedPathChecker { |
| + public: |
| + bool CausingConflict(const syncable::Entry& e, |
| + const syncable::Entry& log_entry) { |
| + return e.good() && e.Get(syncable::IS_DEL) && e.Get(syncable::IS_UNSYNCED); |
| + } |
| + // returns 0 if we should stop investigating the path. |
| + syncable::Id GetAndExamineParent(syncable::BaseTransaction* trans, |
| + syncable::Id id, |
| + syncable::Id check_id, |
| + const syncable::Entry& log_entry) { |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, id); |
| + if (!parent.good()) |
| + return syncable::kNullId; |
| + syncable::Id parent_id = parent.Get(syncable::PARENT_ID); |
| + if (parent_id == check_id) |
| + return syncable::kNullId; |
| + return parent_id; |
| + } |
| +}; |
| + |
| +template <typename Checker> |
| +void CrawlDeletedTreeMergingSets(syncable::BaseTransaction* trans, |
| + const syncable::Entry& entry, |
| + ConflictResolutionView* view, |
| + Checker checker) { |
| + syncable::Id parent_id = entry.Get(syncable::PARENT_ID); |
| + syncable::Id double_step_parent_id = parent_id; |
| + // This block builds sets where we've got an entry in a directory the |
| + // server wants to delete. |
| + // |
| + // Here we're walking up the tree to find all entries that the pass checks |
| + // deleted. We can be extremely strict here as anything unexpected means |
| + // invariants in the local hierarchy have been broken. |
| + while (!parent_id.IsRoot()) { |
| + if (!double_step_parent_id.IsRoot()) { |
| + // Checks to ensure we don't loop. |
| + double_step_parent_id = checker.GetAndExamineParent( |
| + trans, double_step_parent_id, parent_id, entry); |
| + double_step_parent_id = checker.GetAndExamineParent( |
| + trans, double_step_parent_id, parent_id, entry); |
| + } |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, parent_id); |
| + if (checker.CausingConflict(parent, entry)) |
| + view->MergeSets(entry.Get(syncable::ID), parent.Get(syncable::ID)); |
| + else |
| + break; |
| + parent_id = parent.Get(syncable::PARENT_ID); |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| +void BuildAndProcessConflictSetsCommand::MergeSetsForNonEmptyDirectories( |
| + syncable::BaseTransaction* trans, syncable::Entry* entry, |
| + ConflictResolutionView* view) { |
| + if (entry->Get(syncable::IS_UNSYNCED) && !entry->Get(syncable::IS_DEL)) { |
| + ServerDeletedPathChecker checker; |
| + CrawlDeletedTreeMergingSets(trans, *entry, view, checker); |
| + } |
| + if (entry->Get(syncable::IS_UNAPPLIED_UPDATE) && |
| + !entry->Get(syncable::SERVER_IS_DEL)) { |
| + syncable::Entry parent(trans, syncable::GET_BY_ID, |
| + entry->Get(syncable::SERVER_PARENT_ID)); |
| + syncable::Id parent_id = entry->Get(syncable::SERVER_PARENT_ID); |
| + if (!parent.good()) |
| + return; |
| + LocallyDeletedPathChecker checker; |
| + if (!checker.CausingConflict(parent, *entry)) |
| + return; |
| + view->MergeSets(entry->Get(syncable::ID), parent.Get(syncable::ID)); |
| + CrawlDeletedTreeMergingSets(trans, parent, view, checker); |
| + } |
| +} |
| + |
| +} // namespace browser_sync |
| Property changes on: chrome\browser\sync\engine\build_and_process_conflict_sets_command.cc |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| + LF |