Skip to content

Commit

Permalink
sync: Retry soon when server returns conflict
Browse files Browse the repository at this point in the history
When the server gives us a conflict response, we ought to fetch updates
in order to figure out what the server is complaining about, resolve any
conflicts locally, then re-commit.

The current syncer, although not intentionally built to handle this
scenario, does the right thing.  It considers the server's return code
to be indicative of a transient error, so it backs off then retries
later.  The retry sync cycle will fetch updates, resolve conflicts, and
recommit, which is exactly what we want.  Unfortunately, the backoff can
take five minutes.

This commit reduces the time spent unnecessarily backing off.  It's not
the cleanest solution, but its implementation is easy and safe.

BUG=157955


Review URL: https://chromiumcodereview.appspot.com/11411041

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@169158 0039d316-1c4b-4281-b951-d872f2087c98
  • Loading branch information
rlarocque@chromium.org committed Nov 21, 2012
1 parent a036232 commit 16dbe4e
Show file tree
Hide file tree
Showing 5 changed files with 25 additions and 17 deletions.
12 changes: 12 additions & 0 deletions sync/engine/backoff_delay_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ TimeDelta BackoffDelayProvider::GetInitialDelay(
return short_initial_backoff_;
}

// When the server tells us we have a conflict, then we should download the
// latest updates so we can see the conflict ourselves, resolve it locally,
// then try again to commit. Running another sync cycle will do all these
// things. There's no need to back off, we can do this immediately.
//
// TODO(sync): We shouldn't need to handle this in BackoffDelayProvider.
// There should be a way to deal with protocol errors before we get to this
// point.
if (state.commit_result == SERVER_RETURN_CONFLICT) {
return short_initial_backoff_;
}

return default_initial_backoff_;
}

Expand Down
8 changes: 8 additions & 0 deletions sync/engine/backoff_delay_provider_unittest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ TEST_F(BackoffDelayProviderTest, GetInitialDelay) {
state.commit_result = NETWORK_CONNECTION_UNAVAILABLE;
EXPECT_EQ(kInitialBackoffImmediateRetrySeconds,
delay->GetInitialDelay(state).InSeconds());

state.commit_result = SERVER_RETURN_CONFLICT;
EXPECT_EQ(kInitialBackoffImmediateRetrySeconds,
delay->GetInitialDelay(state).InSeconds());
}

TEST_F(BackoffDelayProviderTest, GetInitialDelayWithOverride) {
Expand Down Expand Up @@ -106,6 +110,10 @@ TEST_F(BackoffDelayProviderTest, GetInitialDelayWithOverride) {
state.commit_result = SERVER_RETURN_MIGRATION_DONE;
EXPECT_EQ(kInitialBackoffImmediateRetrySeconds,
delay->GetInitialDelay(state).InSeconds());

state.commit_result = SERVER_RETURN_CONFLICT;
EXPECT_EQ(kInitialBackoffImmediateRetrySeconds,
delay->GetInitialDelay(state).InSeconds());
}

} // namespace syncer
18 changes: 3 additions & 15 deletions sync/engine/process_commit_response_command.cc
Original file line number Diff line number Diff line change
Expand Up @@ -171,21 +171,9 @@ SyncerError ProcessCommitResponseCommand::ProcessCommitResponse(
// to resolve the conflict. That's not what this client does.
//
// We don't currently have any code to support that exceptional control
// flow. We don't intend to add any because this response code will be
// deprecated soon. Instead, we handle this in the same way that we handle
// transient errors. We abort the current sync cycle, wait a little while,
// then try again. The retry sync cycle will attempt to download updates
// which should be sufficient to trigger client-side conflict resolution.
//
// Not treating this as an error would be dangerous. There's a risk that
// the commit loop would loop indefinitely. The loop won't exit until the
// number of unsynced items hits zero or an error is detected. If we're
// constantly receiving conflict responses and we don't treat them as
// errors, there would be no reason to leave that loop.
//
// TODO: Remove this option when the CONFLICT return value is fully
// deprecated.
return SERVER_RETURN_TRANSIENT_ERROR;
// flow. Instead, we abort the current sync cycle and start a new one. The
// end result is the same.
return SERVER_RETURN_CONFLICT;
} else {
LOG(FATAL) << "Inconsistent counts when processing commit response";
return SYNCER_OK;
Expand Down
2 changes: 1 addition & 1 deletion sync/internal_api/public/util/syncer_error.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ namespace syncer {
const char* GetSyncerErrorString(SyncerError value) {
switch (value) {
ENUM_CASE(UNSET);
ENUM_CASE(DIRECTORY_LOOKUP_FAILED);
ENUM_CASE(CANNOT_DO_WORK);
ENUM_CASE(NETWORK_CONNECTION_UNAVAILABLE);
ENUM_CASE(NETWORK_IO_ERROR);
Expand All @@ -25,6 +24,7 @@ const char* GetSyncerErrorString(SyncerError value) {
ENUM_CASE(SERVER_RETURN_MIGRATION_DONE);
ENUM_CASE(SERVER_RETURN_CLEAR_PENDING);
ENUM_CASE(SERVER_RETURN_NOT_MY_BIRTHDAY);
ENUM_CASE(SERVER_RETURN_CONFLICT);
ENUM_CASE(SERVER_RESPONSE_VALIDATION_FAILED);
ENUM_CASE(SYNCER_OK);
}
Expand Down
2 changes: 1 addition & 1 deletion sync/internal_api/public/util/syncer_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ namespace syncer {
// it makes refactoring easier.
enum SyncerError {
UNSET = 0, // Default value.
DIRECTORY_LOOKUP_FAILED, // Local directory lookup failure.
CANNOT_DO_WORK, // A model worker could not process a work item.

NETWORK_CONNECTION_UNAVAILABLE, // Connectivity failure.
Expand All @@ -36,6 +35,7 @@ enum SyncerError {
SERVER_RETURN_MIGRATION_DONE,
SERVER_RETURN_CLEAR_PENDING,
SERVER_RETURN_NOT_MY_BIRTHDAY,
SERVER_RETURN_CONFLICT,
SERVER_RESPONSE_VALIDATION_FAILED,

SYNCER_OK
Expand Down

0 comments on commit 16dbe4e

Please sign in to comment.