diff options
author | asargent <asargent@chromium.org> | 2016-02-19 17:49:15 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-02-20 01:50:33 +0000 |
commit | d40211da2bf6d6b3a6d56e6f9bd26506249339b9 (patch) | |
tree | 8699299d2c972ad47ca05fcadcee7e69e7a951fb /sync | |
parent | 50cc532c23e1029ccec3a49da53a666a7d09fe79 (diff) | |
download | chromium_src-d40211da2bf6d6b3a6d56e6f9bd26506249339b9.zip chromium_src-d40211da2bf6d6b3a6d56e6f9bd26506249339b9.tar.gz chromium_src-d40211da2bf6d6b3a6d56e6f9bd26506249339b9.tar.bz2 |
Change sync conflict resolution for extensions
For extensions and apps, if the server has seen a delete action
(corresponding to an uninstall) and the local state has a not-yet-synced
change, we would prefer to resolve this conflict by keeping the server
state and uninstalling locally. This helps to prevent uninstalled
extensions "coming back to life".
BUG=517910
Review URL: https://codereview.chromium.org/1664643003
Cr-Commit-Position: refs/heads/master@{#376603}
Diffstat (limited to 'sync')
-rw-r--r-- | sync/engine/conflict_resolver.cc | 58 | ||||
-rw-r--r-- | sync/engine/syncer_unittest.cc | 85 |
2 files changed, 118 insertions, 25 deletions
diff --git a/sync/engine/conflict_resolver.cc b/sync/engine/conflict_resolver.cc index 0af3097..d32460c 100644 --- a/sync/engine/conflict_resolver.cc +++ b/sync/engine/conflict_resolver.cc @@ -116,6 +116,8 @@ void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, // f) Otherwise, it's in general safer to ignore local changes, with the // exception of deletion conflicts (choose to undelete) and conflicts // where the non_unique_name or parent don't match. + // e) Except for the case of extensions and apps, where we want uninstalls to + // win over local modifications to avoid "back from the dead" reinstalls. if (!entry.GetServerIsDel()) { // TODO(nick): The current logic is arbitrary; instead, it ought to be made // consistent with the ModelAssociator behavior for a datatype. It would @@ -228,28 +230,42 @@ void ConflictResolver::ProcessSimpleConflict(WriteTransaction* trans, // specifics. entry.PutBaseServerSpecifics(sync_pb::EntitySpecifics()); } else { // SERVER_IS_DEL is true - if (entry.GetIsDir()) { - Directory::Metahandles children; - trans->directory()->GetChildHandlesById(trans, - entry.GetId(), - &children); - // If a server deleted folder has local contents it should be a hierarchy - // conflict. Hierarchy conflicts should not be processed by this - // function. - DCHECK(children.empty()); - } + ModelType type = entry.GetModelType(); + if (type == EXTENSIONS || type == APPS) { + // Ignore local changes for extensions/apps when server had a delete, to + // avoid unwanted reinstall of an uninstalled extension. + DVLOG(1) << "Resolving simple conflict, ignoring local changes for " + << "extension/app: " << entry; + conflict_util::IgnoreLocalChanges(&entry); + status->increment_num_local_overwrites(); + counters->num_local_overwrites++; + UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", + OVERWRITE_LOCAL, + CONFLICT_RESOLUTION_SIZE); + } else { + if (entry.GetIsDir()) { + Directory::Metahandles children; + trans->directory()->GetChildHandlesById(trans, + entry.GetId(), + &children); + // If a server deleted folder has local contents it should be a + // hierarchy conflict. Hierarchy conflicts should not be processed by + // this function. + DCHECK(children.empty()); + } - // The entry is deleted on the server but still exists locally. - // We undelete it by overwriting the server's tombstone with the local - // data. - conflict_util::OverwriteServerChanges(&entry); - status->increment_num_server_overwrites(); - counters->num_server_overwrites++; - DVLOG(1) << "Resolving simple conflict, undeleting server entry: " - << entry; - UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", - UNDELETE, - CONFLICT_RESOLUTION_SIZE); + // The entry is deleted on the server but still exists locally. + // We undelete it by overwriting the server's tombstone with the local + // data. + conflict_util::OverwriteServerChanges(&entry); + status->increment_num_server_overwrites(); + counters->num_server_overwrites++; + DVLOG(1) << "Resolving simple conflict, undeleting server entry: " + << entry; + UMA_HISTOGRAM_ENUMERATION("Sync.ResolveSimpleConflict", + UNDELETE, + CONFLICT_RESOLUTION_SIZE); + } } } diff --git a/sync/engine/syncer_unittest.cc b/sync/engine/syncer_unittest.cc index b2a92de..b968e92 100644 --- a/sync/engine/syncer_unittest.cc +++ b/sync/engine/syncer_unittest.cc @@ -283,6 +283,7 @@ class SyncerTest : public testing::Test, &cancelation_signal_)); debug_info_getter_.reset(new MockDebugInfoGetter); EnableDatatype(BOOKMARKS); + EnableDatatype(EXTENSIONS); EnableDatatype(NIGORI); EnableDatatype(PREFERENCES); EnableDatatype(NIGORI); @@ -2670,11 +2671,11 @@ TEST_F(SyncerTest, CommitsUpdateDoesntAlterEntry) { } TEST_F(SyncerTest, ParentAndChildBothMatch) { - // Disable PREFERENCES which is enabled at the setup step to avoid - // auto-creating - // PREFERENCES root folder and failing the test below that verifies the number - // of children at the root. + // Disable PREFERENCES and EXTENSIONS which are enabled at the setup step to + // avoid auto-creating root folders and failing the test below + // that verifies the number of children at the root. DisableDatatype(PREFERENCES); + DisableDatatype(EXTENSIONS); const FullModelTypeSet all_types = FullModelTypeSet::All(); syncable::Id parent_id = ids_.NewServerId(); @@ -3755,6 +3756,82 @@ TEST_F(SyncerTest, ConflictResolverMergesLocalDeleteAndServerUpdate) { } } +// This ensures that for extensions, we resolve the conflict of local updates +// and server deletes in favor of the server, to prevent extensions from +// being reinstalled after uninstall. +TEST_F(SyncerTest, ConflictResolverAcceptsServerDeleteForExtensions) { + ASSERT_TRUE(context_->GetEnabledTypes().Has(EXTENSIONS)); + + // Create an extension entry. + int64_t metahandle; + { + WriteTransaction trans(FROM_HERE, UNITTEST, directory()); + MutableEntry extension( + &trans, CREATE, EXTENSIONS, trans.root_id(), "extension_name"); + ASSERT_TRUE(extension.good()); + sync_pb::EntitySpecifics specifics; + AddDefaultFieldValue(EXTENSIONS, &specifics); + extension.PutSpecifics(specifics); + EXPECT_FALSE(extension.GetIsUnappliedUpdate()); + EXPECT_FALSE(extension.GetId().ServerKnows()); + metahandle = extension.GetMetahandle(); + extension.PutIsUnsynced(true); + } + + // Make sure the server has received the new item. + SyncShareNudge(); + syncable::Id id; + { + syncable::ReadTransaction trans(FROM_HERE, directory()); + Entry entry(&trans, GET_BY_HANDLE, metahandle); + + EXPECT_EQ(metahandle, entry.GetMetahandle()); + EXPECT_FALSE(entry.GetIsDel()); + EXPECT_FALSE(entry.GetServerIsDel()); + EXPECT_GE(entry.GetBaseVersion(), 0); + EXPECT_EQ(entry.GetBaseVersion(), entry.GetServerVersion()); + EXPECT_FALSE(entry.GetIsUnsynced()); + EXPECT_FALSE(entry.GetIsUnappliedUpdate()); + id = entry.GetId(); + } + + + // Simulate another client deleting the item. + { + syncable::ReadTransaction trans(FROM_HERE, directory()); + Entry entry(&trans, GET_BY_HANDLE, metahandle); + mock_server_->AddUpdateTombstone(id, EXTENSIONS); + } + + // Create a local update, which should cause a conflict with the delete that + // we just pushed to the server. + { + WriteTransaction trans(FROM_HERE, UNITTEST, directory()); + MutableEntry extension(&trans, GET_BY_HANDLE, metahandle); + ASSERT_TRUE(extension.good()); + sync_pb::EntitySpecifics specifics; + AddDefaultFieldValue(EXTENSIONS, &specifics); + specifics.mutable_extension()->set_disable_reasons(2); + extension.PutSpecifics(specifics); + EXPECT_FALSE(extension.GetIsUnappliedUpdate()); + extension.PutIsUnsynced(true); + } + + // Run a sync, and expect the item to be deleted. + SyncShareNudge(); + { + syncable::ReadTransaction trans(FROM_HERE, directory()); + Entry entry(&trans, GET_BY_HANDLE, metahandle); + EXPECT_EQ(metahandle, entry.GetMetahandle()); + EXPECT_TRUE(entry.GetIsDel()); + EXPECT_TRUE(entry.GetServerIsDel()); + EXPECT_FALSE(entry.GetIsUnsynced()); + EXPECT_FALSE(entry.GetIsUnappliedUpdate()); + EXPECT_GE(entry.GetBaseVersion(), 0); + EXPECT_GE(entry.GetServerVersion(), 0); + } +} + // See what happens if the IS_DIR bit gets flipped. This can cause us // all kinds of disasters. TEST_F(SyncerTest, UpdateFlipsTheFolderBit) { |