summaryrefslogtreecommitdiffstats
path: root/chrome/browser/sync/engine/process_commit_response_command.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/sync/engine/process_commit_response_command.cc')
-rw-r--r--chrome/browser/sync/engine/process_commit_response_command.cc374
1 files changed, 374 insertions, 0 deletions
diff --git a/chrome/browser/sync/engine/process_commit_response_command.cc b/chrome/browser/sync/engine/process_commit_response_command.cc
new file mode 100644
index 0000000..6a2a177
--- /dev/null
+++ b/chrome/browser/sync/engine/process_commit_response_command.cc
@@ -0,0 +1,374 @@
+// 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/process_commit_response_command.h"
+
+#include <set>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "chrome/browser/sync/engine/syncer_util.h"
+#include "chrome/browser/sync/engine/syncer_proto_util.h"
+#include "chrome/browser/sync/engine/syncer_session.h"
+#include "chrome/browser/sync/engine/syncer_status.h"
+#include "chrome/browser/sync/engine/syncproto.h"
+#include "chrome/browser/sync/syncable/directory_manager.h"
+#include "chrome/browser/sync/syncable/syncable.h"
+#include "chrome/browser/sync/util/character_set_converters.h"
+
+using syncable::ScopedDirLookup;
+using syncable::WriteTransaction;
+using syncable::MutableEntry;
+using syncable::Entry;
+using syncable::Name;
+using syncable::SyncName;
+using syncable::DBName;
+
+using std::set;
+using std::vector;
+
+using syncable::BASE_VERSION;
+using syncable::GET_BY_ID;
+using syncable::ID;
+using syncable::IS_DEL;
+using syncable::IS_DIR;
+using syncable::IS_UNAPPLIED_UPDATE;
+using syncable::IS_UNSYNCED;
+using syncable::PARENT_ID;
+using syncable::SERVER_PARENT_ID;
+using syncable::SERVER_POSITION_IN_PARENT;
+using syncable::SYNCER;
+using syncable::SYNCING;
+
+namespace browser_sync {
+
+void IncrementErrorCounters(SyncerStatus status) {
+ status.increment_consecutive_problem_commits();
+ status.increment_consecutive_errors();
+}
+void ResetErrorCounters(SyncerStatus status) {
+ status.zero_consecutive_problem_commits();
+ status.zero_consecutive_errors();
+}
+
+ProcessCommitResponseCommand::ProcessCommitResponseCommand() {}
+ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {}
+
+void ProcessCommitResponseCommand::ModelChangingExecuteImpl(
+ SyncerSession *session) {
+ // TODO(sync): This function returns if it sees problems. We probably want
+ // to flag the need for an update or similar.
+ ScopedDirLookup dir(session->dirman(), session->account_name());
+ if (!dir.good()) {
+ LOG(ERROR) << "Scoped dir lookup failed!";
+ return;
+ }
+ const ClientToServerResponse& response = session->commit_response();
+ const vector<syncable::Id>& commit_ids = session->commit_ids();
+
+ // TODO(sync): move counters out of here.
+ SyncerStatus status(session);
+
+ if (!response.has_commit()) {
+ // TODO(sync): What if we didn't try to commit anything?
+ LOG(WARNING) << "Commit response has no commit body!";
+ IncrementErrorCounters(status);
+ return;
+ }
+
+ const CommitResponse& cr = response.commit();
+ int commit_count = commit_ids.size();
+ if (cr.entryresponse_size() != commit_count) {
+ LOG(ERROR) << "Commit response has wrong number of entries! Expected:" <<
+ commit_count << " Got:" << cr.entryresponse_size();
+ for (int i = 0 ; i < cr.entryresponse_size() ; i++) {
+ LOG(ERROR) << "Response #" << i << " Value: " <<
+ cr.entryresponse(i).response_type();
+ if (cr.entryresponse(i).has_error_message())
+ LOG(ERROR) << " " << cr.entryresponse(i).error_message();
+ }
+ IncrementErrorCounters(status);
+ return;
+ }
+
+ // If we try to commit a parent and child together and the parent conflicts
+ // the child will have a bad parent causing an error. As this is not a
+ // critical error, we trap it and don't LOG(ERROR). To enable this we keep
+ // a map of conflicting new folders.
+ int transient_error_commits = 0;
+ int conflicting_commits = 0;
+ int error_commits = 0;
+ int successes = 0;
+ bool over_quota = false;
+ set<syncable::Id> conflicting_new_folder_ids;
+ set<syncable::Id> deleted_folders;
+ bool truncated_commit_logged = false;
+ { // Scope for WriteTransaction
+ WriteTransaction trans(dir, SYNCER, __FILE__, __LINE__);
+ for (int i = 0; i < cr.entryresponse_size(); i++) {
+ CommitResponse::RESPONSE_TYPE response_type =
+ ProcessSingleCommitResponse(&trans, cr.entryresponse(i),
+ commit_ids[i],
+ &conflicting_new_folder_ids,
+ &deleted_folders, session);
+ switch (response_type) {
+ case CommitResponse::INVALID_MESSAGE:
+ ++error_commits;
+ break;
+ case CommitResponse::CONFLICT:
+ ++conflicting_commits;
+ session->AddCommitConflict(commit_ids[i]);
+ break;
+ case CommitResponse::SUCCESS:
+ // TODO(sync): worry about sync_rate_ rate calc?
+ ++successes;
+ status.increment_successful_commits();
+ break;
+ case CommitResponse::OVER_QUOTA:
+ over_quota = true;
+ // We handle over quota like a retry.
+ case CommitResponse::RETRY:
+ session->AddBlockedItem(commit_ids[i]);
+ break;
+ case CommitResponse::TRANSIENT_ERROR:
+ ++transient_error_commits;
+ break;
+ default:
+ LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
+ }
+ }
+ }
+
+ // TODO(sync): move status reporting elsewhere.
+ status.set_conflicting_commits(conflicting_commits);
+ status.set_error_commits(error_commits);
+ if (0 == successes) {
+ status.increment_consecutive_transient_error_commits_by(
+ transient_error_commits);
+ status.increment_consecutive_errors_by(transient_error_commits);
+ } else {
+ status.zero_consecutive_transient_error_commits();
+ status.zero_consecutive_errors();
+ }
+ // If all commits are errors count it as an error.
+ if (commit_count == error_commits) {
+ // A larger error step than normal because a POST just succeeded.
+ status.TallyBigNewError();
+ }
+ if (commit_count != (conflicting_commits + error_commits +
+ transient_error_commits)) {
+ ResetErrorCounters(status);
+ }
+ SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders);
+ session->set_over_quota(over_quota);
+
+ return;
+}
+
+void LogServerError(const CommitResponse_EntryResponse& res) {
+ if (res.has_error_message())
+ LOG(ERROR) << " " << res.error_message();
+ else
+ LOG(ERROR) << " No detailed error message returned from server";
+}
+
+CommitResponse::RESPONSE_TYPE
+ProcessCommitResponseCommand::ProcessSingleCommitResponse(
+ syncable::WriteTransaction* trans,
+ const sync_pb::CommitResponse_EntryResponse& pb_server_entry,
+ const syncable::Id& pre_commit_id,
+ std::set<syncable::Id>* conflicting_new_folder_ids,
+ set<syncable::Id>* deleted_folders,
+ SyncerSession* const session) {
+
+ const CommitResponse_EntryResponse& server_entry =
+ *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry);
+ MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id);
+ CHECK(local_entry.good());
+ bool syncing_was_set = local_entry.Get(SYNCING);
+ local_entry.Put(SYNCING, false);
+
+ CommitResponse::RESPONSE_TYPE response = (CommitResponse::RESPONSE_TYPE)
+ server_entry.response_type();
+ if (!CommitResponse::RESPONSE_TYPE_IsValid(response)) {
+ LOG(ERROR) << "Commit response has unknown response type! Possibly out "
+ "of date client?";
+ return CommitResponse::INVALID_MESSAGE;
+ }
+ if (CommitResponse::TRANSIENT_ERROR == response) {
+ LOG(INFO) << "Transient Error Committing: " << local_entry;
+ LogServerError(server_entry);
+ return CommitResponse::TRANSIENT_ERROR;
+ }
+ if (CommitResponse::INVALID_MESSAGE == response) {
+ LOG(ERROR) << "Error Commiting: " << local_entry;
+ LogServerError(server_entry);
+ return response;
+ }
+ if (CommitResponse::CONFLICT == response) {
+ LOG(INFO) << "Conflict Committing: " << local_entry;
+ if (!pre_commit_id.ServerKnows() && local_entry.Get(IS_DIR)) {
+ conflicting_new_folder_ids->insert(pre_commit_id);
+ }
+ return response;
+ }
+ if (CommitResponse::RETRY == response) {
+ LOG(INFO) << "Retry Committing: " << local_entry;
+ return response;
+ }
+ if (CommitResponse::OVER_QUOTA == response) {
+ LOG(INFO) << "Hit Quota Committing: " << local_entry;
+ return response;
+ }
+ if (!server_entry.has_id_string()) {
+ LOG(ERROR) << "Commit response has no id";
+ return CommitResponse::INVALID_MESSAGE;
+ }
+
+ // implied by the IsValid call above, but here for clarity.
+ DCHECK_EQ(CommitResponse::SUCCESS, response) << response;
+ // Check to see if we've been given the ID of an existing entry. If so treat
+ // it as an error response and retry later.
+ if (pre_commit_id != server_entry.id()) {
+ Entry e(trans, GET_BY_ID, server_entry.id());
+ if (e.good()) {
+ LOG(ERROR) << "Got duplicate id when commiting id: " << pre_commit_id <<
+ ". Treating as an error return";
+ return CommitResponse::INVALID_MESSAGE;
+ }
+ }
+
+ if (server_entry.version() == 0) {
+ LOG(WARNING) << "Server returned a zero version on a commit response.";
+ }
+
+ ProcessSuccessfulCommitResponse(trans, server_entry, pre_commit_id,
+ &local_entry, syncing_was_set,
+ deleted_folders, session);
+ return response;
+}
+
+void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
+ syncable::WriteTransaction* trans,
+ const CommitResponse_EntryResponse& server_entry,
+ const syncable::Id& pre_commit_id, syncable::MutableEntry* local_entry,
+ bool syncing_was_set, set<syncable::Id>* deleted_folders,
+ SyncerSession* const session) {
+ int64 old_version = local_entry->Get(BASE_VERSION);
+ int64 new_version = server_entry.version();
+ bool bad_commit_version = false;
+ // TODO(sync): The !server_entry.has_id_string() clauses below were
+ // introduced when working with the new protocol.
+ if (!pre_commit_id.ServerKnows())
+ bad_commit_version = 0 == new_version;
+ else
+ bad_commit_version = old_version > new_version;
+ if (bad_commit_version) {
+ LOG(ERROR) << "Bad version in commit return for " << *local_entry <<
+ " new_id:" << server_entry.id() << " new_version:" <<
+ server_entry.version();
+ return;
+ }
+ if (server_entry.id() != pre_commit_id) {
+ if (pre_commit_id.ServerKnows()) {
+ // TODO(sync): In future it's possible that we'll want the opportunity
+ // to do a server triggered move aside here.
+ LOG(ERROR) << " ID change but not committing a new entry. " <<
+ pre_commit_id << " became " << server_entry.id() << ".";
+ return;
+ }
+ if (!server_entry.id().ServerKnows()) {
+ LOG(ERROR) << " New entries id < 0." << pre_commit_id << " became " <<
+ server_entry.id() << ".";
+ return;
+ }
+ MutableEntry same_id(trans, GET_BY_ID, server_entry.id());
+ // We should trap this before this function.
+ CHECK(!same_id.good()) << "ID clash with id " << server_entry.id() <<
+ " during commit " << same_id;
+ SyncerUtil::ChangeEntryIDAndUpdateChildren(
+ trans, local_entry, server_entry.id());
+ LOG(INFO) << "Changing ID to " << server_entry.id();
+ }
+
+ local_entry->Put(BASE_VERSION, new_version);
+ LOG(INFO) << "Commit is changing base version of " <<
+ local_entry->Get(ID) << " to: " << new_version;
+
+ if (local_entry->Get(IS_UNAPPLIED_UPDATE)) {
+ // This is possible, but very unlikely.
+ local_entry->Put(IS_UNAPPLIED_UPDATE, false);
+ }
+
+ if (server_entry.has_name()) {
+ if (syncing_was_set) {
+ PerformCommitTimeNameAside(trans, server_entry, local_entry);
+ } else {
+ // IS_UNSYNCED will ensure that this entry gets committed again,
+ // even if we skip this name aside. IS_UNSYNCED was probably previously
+ // set, but let's just set it anyway.
+ local_entry->Put(IS_UNSYNCED, true);
+ LOG(INFO) << "Skipping commit time name aside because" <<
+ " entry was changed during commit.";
+ }
+ }
+
+ if (syncing_was_set && server_entry.has_position_in_parent()) {
+ // The server has the final say on positioning, so apply the absolute
+ // position that it returns.
+ local_entry->Put(SERVER_POSITION_IN_PARENT,
+ server_entry.position_in_parent());
+
+ // We just committed successfully, so we assume that the position
+ // value we got applies to the PARENT_ID we submitted.
+ syncable::Id new_prev = SyncerUtil::ComputePrevIdFromServerPosition(
+ trans, local_entry, local_entry->Get(PARENT_ID));
+ if (!local_entry->PutPredecessor(new_prev)) {
+ LOG(WARNING) << "PutPredecessor failed after successful commit";
+ }
+ }
+
+ if (syncing_was_set) {
+ local_entry->Put(IS_UNSYNCED, false);
+ }
+ if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) {
+ deleted_folders->insert(local_entry->Get(ID));
+ }
+}
+
+void ProcessCommitResponseCommand::PerformCommitTimeNameAside(
+ syncable::WriteTransaction* trans,
+ const CommitResponse_EntryResponse& server_entry,
+ syncable::MutableEntry* local_entry) {
+ Name old_name(local_entry->GetName());
+
+ // Ensure that we don't collide with an existing entry.
+ SyncName server_name =
+ SyncerProtoUtil::NameFromCommitEntryResponse(server_entry);
+
+ LOG(INFO) << "Server provided committed name:" << server_name.value();
+ if (!server_name.value().empty() &&
+ static_cast<SyncName&>(old_name) != server_name) {
+ LOG(INFO) << "Server name differs from local name, attempting"
+ << " commit time name aside.";
+
+ DBName db_name(server_name.value());
+ db_name.MakeOSLegal();
+
+ // This is going to produce ~1 names instead of (Edited) names.
+ // Since this should be EXTREMELY rare, we do this for now.
+ db_name.MakeNoncollidingForEntry(trans, local_entry->Get(SERVER_PARENT_ID),
+ local_entry);
+
+ CHECK(!db_name.empty());
+
+ LOG(INFO) << "Server commit moved aside entry: " << old_name.db_value()
+ << " to new name " << db_name;
+
+ // Should be safe since we're in a "commit lock."
+ local_entry->PutName(Name::FromDBNameAndSyncName(db_name, server_name));
+ }
+}
+
+} // namespace browser_sync