Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "sync/engine/process_commit_response_command.h" | 5 #include "sync/engine/process_commit_response_command.h" |
| 6 | 6 |
| 7 #include <cstddef> | 7 #include <cstddef> |
| 8 #include <set> | 8 #include <set> |
| 9 #include <string> | 9 #include <string> |
| 10 #include <vector> | 10 #include <vector> |
| (...skipping 30 matching lines...) Expand all Loading... | |
| 41 using syncable::SYNCER; | 41 using syncable::SYNCER; |
| 42 using syncable::SYNCING; | 42 using syncable::SYNCING; |
| 43 | 43 |
| 44 namespace browser_sync { | 44 namespace browser_sync { |
| 45 | 45 |
| 46 using sessions::OrderedCommitSet; | 46 using sessions::OrderedCommitSet; |
| 47 using sessions::StatusController; | 47 using sessions::StatusController; |
| 48 using sessions::SyncSession; | 48 using sessions::SyncSession; |
| 49 using sessions::ConflictProgress; | 49 using sessions::ConflictProgress; |
| 50 | 50 |
| 51 ProcessCommitResponseCommand::ProcessCommitResponseCommand() {} | 51 ProcessCommitResponseCommand::ProcessCommitResponseCommand( |
| 52 const sessions::OrderedCommitSet& commit_set, | |
| 53 const ClientToServerMessage& commit_message, | |
| 54 const ClientToServerResponse& commit_response) | |
| 55 : commit_set_(commit_set), | |
| 56 commit_message_(commit_message), | |
| 57 commit_response_(commit_response) { | |
| 58 } | |
| 59 | |
| 52 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {} | 60 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {} |
| 53 | 61 |
| 54 std::set<ModelSafeGroup> ProcessCommitResponseCommand::GetGroupsToChange( | 62 std::set<ModelSafeGroup> ProcessCommitResponseCommand::GetGroupsToChange( |
| 55 const sessions::SyncSession& session) const { | 63 const sessions::SyncSession& session) const { |
| 56 std::set<ModelSafeGroup> groups_with_commits; | 64 std::set<ModelSafeGroup> groups_with_commits; |
| 57 | 65 |
| 58 syncable::Directory* dir = session.context()->directory(); | 66 syncable::Directory* dir = session.context()->directory(); |
| 59 syncable::ReadTransaction trans(FROM_HERE, dir); | 67 syncable::ReadTransaction trans(FROM_HERE, dir); |
| 60 const StatusController& status = session.status_controller(); | 68 for (size_t i = 0; i < commit_set_.Size(); ++i) { |
| 61 for (size_t i = 0; i < status.commit_ids().size(); ++i) { | |
| 62 groups_with_commits.insert( | 69 groups_with_commits.insert( |
| 63 GetGroupForModelType(status.GetUnrestrictedCommitModelTypeAt(i), | 70 GetGroupForModelType(commit_set_.GetModelTypeAt(i), |
| 64 session.routing_info())); | 71 session.routing_info())); |
| 65 } | 72 } |
| 66 | 73 |
| 67 return groups_with_commits; | 74 return groups_with_commits; |
| 68 } | 75 } |
| 69 | 76 |
| 70 SyncerError ProcessCommitResponseCommand::ModelNeutralExecuteImpl( | 77 SyncerError ProcessCommitResponseCommand::ModelNeutralExecuteImpl( |
| 71 sessions::SyncSession* session) { | 78 SyncSession* session) { |
| 72 const StatusController& status = session->status_controller(); | 79 syncable::Directory* dir = session->context()->directory(); |
| 73 const ClientToServerResponse& response(status.commit_response()); | 80 const vector<syncable::Id>& commit_ids = commit_set_.GetAllCommitIds(); |
| 74 const vector<syncable::Id>& commit_ids(status.commit_ids()); | |
| 75 | 81 |
| 76 if (!response.has_commit()) { | 82 if (!commit_response_.has_commit()) { |
| 77 // TODO(sync): What if we didn't try to commit anything? | |
| 78 LOG(WARNING) << "Commit response has no commit body!"; | 83 LOG(WARNING) << "Commit response has no commit body!"; |
| 84 ClearSyncingBits(dir, commit_ids); | |
| 79 return SERVER_RESPONSE_VALIDATION_FAILED; | 85 return SERVER_RESPONSE_VALIDATION_FAILED; |
| 80 } | 86 } |
| 81 | 87 |
| 82 const CommitResponse& cr = response.commit(); | 88 const CommitResponse& cr = commit_response_.commit(); |
| 83 int commit_count = commit_ids.size(); | 89 int commit_count = commit_set_.Size(); |
| 84 if (cr.entryresponse_size() != commit_count) { | 90 if (cr.entryresponse_size() != commit_count) { |
| 85 LOG(ERROR) << "Commit response has wrong number of entries! Expected:" << | 91 LOG(ERROR) << "Commit response has wrong number of entries! Expected:" << |
| 86 commit_count << " Got:" << cr.entryresponse_size(); | 92 commit_count << " Got:" << cr.entryresponse_size(); |
| 87 for (int i = 0 ; i < cr.entryresponse_size() ; i++) { | 93 for (int i = 0 ; i < cr.entryresponse_size() ; i++) { |
| 88 LOG(ERROR) << "Response #" << i << " Value: " << | 94 LOG(ERROR) << "Response #" << i << " Value: " << |
| 89 cr.entryresponse(i).response_type(); | 95 cr.entryresponse(i).response_type(); |
| 90 if (cr.entryresponse(i).has_error_message()) | 96 if (cr.entryresponse(i).has_error_message()) |
| 91 LOG(ERROR) << " " << cr.entryresponse(i).error_message(); | 97 LOG(ERROR) << " " << cr.entryresponse(i).error_message(); |
| 92 } | 98 } |
| 99 ClearSyncingBits(dir, commit_ids); | |
| 93 return SERVER_RESPONSE_VALIDATION_FAILED; | 100 return SERVER_RESPONSE_VALIDATION_FAILED; |
| 94 } | 101 } |
| 95 return SYNCER_OK; | 102 return SYNCER_OK; |
| 96 } | 103 } |
| 97 | 104 |
| 98 SyncerError ProcessCommitResponseCommand::ModelChangingExecuteImpl( | 105 SyncerError ProcessCommitResponseCommand::ModelChangingExecuteImpl( |
| 99 SyncSession* session) { | 106 SyncSession* session) { |
| 100 SyncerError result = ProcessCommitResponse(session); | 107 SyncerError result = ProcessCommitResponse(session); |
| 101 ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor(); | 108 ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor(); |
| 102 if (session->status_controller().HasBookmarkCommitActivity() && | 109 |
| 103 session->status_controller().syncer_status() | 110 // This is to be run on one model only: the bookmark model. |
| 111 if (session->status_controller() | |
| 112 .ActiveGroupRestrictionIncludesModel(syncable::BOOKMARKS)) { | |
| 113 // If the commit failed, return the data to the ExtensionsActivityMonitor. | |
| 114 if (session->status_controller().syncer_status() | |
| 104 .num_successful_bookmark_commits == 0) { | 115 .num_successful_bookmark_commits == 0) { |
| 105 monitor->PutRecords(session->extensions_activity()); | 116 monitor->PutRecords(session->extensions_activity()); |
| 117 } | |
| 118 // Clear our cached data in either case. | |
| 106 session->mutable_extensions_activity()->clear(); | 119 session->mutable_extensions_activity()->clear(); |
| 107 } | 120 } |
| 121 | |
| 108 return result; | 122 return result; |
| 109 } | 123 } |
| 110 | 124 |
| 111 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse( | 125 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse( |
| 112 SyncSession* session) { | 126 SyncSession* session) { |
| 113 syncable::Directory* dir = session->context()->directory(); | 127 syncable::Directory* dir = session->context()->directory(); |
| 128 StatusController* status = session->mutable_status_controller(); | |
| 129 const CommitResponse& cr = commit_response_.commit(); | |
| 130 const sync_pb::CommitMessage& commit_message = commit_message_.commit(); | |
| 114 | 131 |
| 115 StatusController* status = session->mutable_status_controller(); | |
| 116 const ClientToServerResponse& response(status->commit_response()); | |
| 117 const CommitResponse& cr = response.commit(); | |
| 118 const sync_pb::CommitMessage& commit_message = | |
| 119 status->commit_message().commit(); | |
| 120 | |
| 121 // If we try to commit a parent and child together and the parent conflicts | |
| 122 // the child will have a bad parent causing an error. As this is not a | |
| 123 // critical error, we trap it and don't LOG(ERROR). To enable this we keep | |
| 124 // a map of conflicting new folders. | |
| 125 int transient_error_commits = 0; | 132 int transient_error_commits = 0; |
| 126 int conflicting_commits = 0; | 133 int conflicting_commits = 0; |
| 127 int error_commits = 0; | 134 int error_commits = 0; |
| 128 int successes = 0; | 135 int successes = 0; |
| 129 set<syncable::Id> conflicting_new_folder_ids; | 136 |
| 130 set<syncable::Id> deleted_folders; | 137 set<syncable::Id> deleted_folders; |
| 131 ConflictProgress* conflict_progress = status->mutable_conflict_progress(); | 138 ConflictProgress* conflict_progress = status->mutable_conflict_progress(); |
| 132 OrderedCommitSet::Projection proj = status->commit_id_projection(); | 139 OrderedCommitSet::Projection proj = status->commit_id_projection( |
| 140 commit_set_); | |
| 141 | |
| 133 if (!proj.empty()) { // Scope for WriteTransaction. | 142 if (!proj.empty()) { // Scope for WriteTransaction. |
| 134 WriteTransaction trans(FROM_HERE, SYNCER, dir); | 143 WriteTransaction trans(FROM_HERE, SYNCER, dir); |
| 135 for (size_t i = 0; i < proj.size(); i++) { | 144 for (size_t i = 0; i < proj.size(); i++) { |
| 136 CommitResponse::ResponseType response_type = | 145 CommitResponse::ResponseType response_type = ProcessSingleCommitResponse( |
| 137 ProcessSingleCommitResponse(&trans, cr.entryresponse(proj[i]), | 146 &trans, |
| 138 commit_message.entries(proj[i]), | 147 cr.entryresponse(proj[i]), |
| 139 status->GetCommitIdAt(proj[i]), | 148 commit_message.entries(proj[i]), |
| 140 &conflicting_new_folder_ids, | 149 commit_set_.GetCommitIdAt(proj[i]), |
| 141 &deleted_folders); | 150 &deleted_folders); |
| 142 switch (response_type) { | 151 switch (response_type) { |
| 143 case CommitResponse::INVALID_MESSAGE: | 152 case CommitResponse::INVALID_MESSAGE: |
| 144 ++error_commits; | 153 ++error_commits; |
| 145 break; | 154 break; |
| 146 case CommitResponse::CONFLICT: | 155 case CommitResponse::CONFLICT: |
| 147 ++conflicting_commits; | 156 ++conflicting_commits; |
| 148 // Only server CONFLICT responses will activate conflict resolution. | |
| 149 conflict_progress->AddServerConflictingItemById( | 157 conflict_progress->AddServerConflictingItemById( |
| 150 status->GetCommitIdAt(proj[i])); | 158 commit_set_.GetCommitIdAt(proj[i])); |
| 151 break; | 159 break; |
| 152 case CommitResponse::SUCCESS: | 160 case CommitResponse::SUCCESS: |
| 153 // TODO(sync): worry about sync_rate_ rate calc? | 161 // TODO(sync): worry about sync_rate_ rate calc? |
| 154 ++successes; | 162 ++successes; |
| 155 if (status->GetCommitModelTypeAt(proj[i]) == syncable::BOOKMARKS) | 163 if (commit_set_.GetModelTypeAt(proj[i]) == syncable::BOOKMARKS) |
| 156 status->increment_num_successful_bookmark_commits(); | 164 status->increment_num_successful_bookmark_commits(); |
| 157 status->increment_num_successful_commits(); | 165 status->increment_num_successful_commits(); |
| 158 break; | 166 break; |
| 159 case CommitResponse::OVER_QUOTA: | 167 case CommitResponse::OVER_QUOTA: |
| 160 // We handle over quota like a retry, which is same as transient. | 168 // We handle over quota like a retry, which is same as transient. |
| 161 case CommitResponse::RETRY: | 169 case CommitResponse::RETRY: |
| 162 case CommitResponse::TRANSIENT_ERROR: | 170 case CommitResponse::TRANSIENT_ERROR: |
| 163 ++transient_error_commits; | 171 ++transient_error_commits; |
| 164 break; | 172 break; |
| 165 default: | 173 default: |
| 166 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse"; | 174 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse"; |
| 167 } | 175 } |
| 168 } | 176 } |
| 169 } | 177 } |
| 170 | 178 |
| 171 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders); | 179 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders); |
| 172 | 180 |
| 173 int commit_count = static_cast<int>(proj.size()); | 181 int commit_count = static_cast<int>(proj.size()); |
| 174 if (commit_count == (successes + conflicting_commits)) { | 182 if (commit_count == successes) { |
| 175 // We consider conflicting commits as a success because things will work out | |
| 176 // on their own when we receive them. Flags will be set so that | |
| 177 // HasMoreToSync() will cause SyncScheduler to enter another sync cycle to | |
| 178 // handle this condition. | |
| 179 return SYNCER_OK; | 183 return SYNCER_OK; |
| 180 } else if (error_commits > 0) { | 184 } else if (error_commits > 0) { |
| 181 return SERVER_RETURN_UNKNOWN_ERROR; | 185 return SERVER_RETURN_UNKNOWN_ERROR; |
| 182 } else if (transient_error_commits > 0) { | 186 } else if (transient_error_commits > 0) { |
| 183 return SERVER_RETURN_TRANSIENT_ERROR; | 187 return SERVER_RETURN_TRANSIENT_ERROR; |
| 188 } else if (conflicting_commits > 0) { | |
| 189 // This means that the server already has an item with this version, but | |
| 190 // we haven't seen that update yet. The correct response is to go back | |
| 191 // to the download upates phase and try to fetch the conflicting update | |
| 192 // so we can resolve it. | |
| 193 // | |
| 194 // The easiest and safest way to handle this is to treat it as a transient | |
| 195 // error. This will cause us to abort this sync cycle, wait a little while, | |
| 196 // then try again starting with a GetUpdate. | |
| 197 // | |
| 198 // TODO: Remove this option when the CONFLICT return value is fully | |
| 199 // deprecated. | |
| 200 return SERVER_RETURN_TRANSIENT_ERROR; | |
|
rlarocque
2012/04/13 02:02:22
You could argue that there should be some other re
| |
| 184 } else { | 201 } else { |
| 185 LOG(FATAL) << "Inconsistent counts when processing commit response"; | 202 LOG(FATAL) << "Inconsistent counts when processing commit response"; |
| 186 return SYNCER_OK; | 203 return SYNCER_OK; |
| 187 } | 204 } |
| 188 } | 205 } |
| 189 | 206 |
| 190 void LogServerError(const CommitResponse_EntryResponse& res) { | 207 void LogServerError(const CommitResponse_EntryResponse& res) { |
| 191 if (res.has_error_message()) | 208 if (res.has_error_message()) |
| 192 LOG(WARNING) << " " << res.error_message(); | 209 LOG(WARNING) << " " << res.error_message(); |
| 193 else | 210 else |
| 194 LOG(WARNING) << " No detailed error message returned from server"; | 211 LOG(WARNING) << " No detailed error message returned from server"; |
| 195 } | 212 } |
| 196 | 213 |
| 197 CommitResponse::ResponseType | 214 CommitResponse::ResponseType |
| 198 ProcessCommitResponseCommand::ProcessSingleCommitResponse( | 215 ProcessCommitResponseCommand::ProcessSingleCommitResponse( |
| 199 syncable::WriteTransaction* trans, | 216 syncable::WriteTransaction* trans, |
| 200 const sync_pb::CommitResponse_EntryResponse& pb_server_entry, | 217 const sync_pb::CommitResponse_EntryResponse& pb_server_entry, |
| 201 const sync_pb::SyncEntity& commit_request_entry, | 218 const sync_pb::SyncEntity& commit_request_entry, |
| 202 const syncable::Id& pre_commit_id, | 219 const syncable::Id& pre_commit_id, |
| 203 std::set<syncable::Id>* conflicting_new_folder_ids, | |
| 204 set<syncable::Id>* deleted_folders) { | 220 set<syncable::Id>* deleted_folders) { |
| 205 | 221 |
| 206 const CommitResponse_EntryResponse& server_entry = | 222 const CommitResponse_EntryResponse& server_entry = |
| 207 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry); | 223 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry); |
| 208 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id); | 224 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id); |
| 209 CHECK(local_entry.good()); | 225 CHECK(local_entry.good()); |
| 210 bool syncing_was_set = local_entry.Get(SYNCING); | 226 bool syncing_was_set = local_entry.Get(SYNCING); |
| 211 local_entry.Put(SYNCING, false); | 227 local_entry.Put(SYNCING, false); |
| 212 | 228 |
| 213 CommitResponse::ResponseType response = (CommitResponse::ResponseType) | 229 CommitResponse::ResponseType response = (CommitResponse::ResponseType) |
| 214 server_entry.response_type(); | 230 server_entry.response_type(); |
| 215 if (!CommitResponse::ResponseType_IsValid(response)) { | 231 if (!CommitResponse::ResponseType_IsValid(response)) { |
| 216 LOG(ERROR) << "Commit response has unknown response type! Possibly out " | 232 LOG(ERROR) << "Commit response has unknown response type! Possibly out " |
| 217 "of date client?"; | 233 "of date client?"; |
| 218 return CommitResponse::INVALID_MESSAGE; | 234 return CommitResponse::INVALID_MESSAGE; |
| 219 } | 235 } |
| 220 if (CommitResponse::TRANSIENT_ERROR == response) { | 236 if (CommitResponse::TRANSIENT_ERROR == response) { |
| 221 DVLOG(1) << "Transient Error Committing: " << local_entry; | 237 DVLOG(1) << "Transient Error Committing: " << local_entry; |
| 222 LogServerError(server_entry); | 238 LogServerError(server_entry); |
| 223 return CommitResponse::TRANSIENT_ERROR; | 239 return CommitResponse::TRANSIENT_ERROR; |
| 224 } | 240 } |
| 225 if (CommitResponse::INVALID_MESSAGE == response) { | 241 if (CommitResponse::INVALID_MESSAGE == response) { |
| 226 LOG(ERROR) << "Error Commiting: " << local_entry; | 242 LOG(ERROR) << "Error Commiting: " << local_entry; |
| 227 LogServerError(server_entry); | 243 LogServerError(server_entry); |
| 228 return response; | 244 return response; |
| 229 } | 245 } |
| 230 if (CommitResponse::CONFLICT == response) { | 246 if (CommitResponse::CONFLICT == response) { |
| 231 DVLOG(1) << "Conflict Committing: " << local_entry; | 247 DVLOG(1) << "Conflict Committing: " << local_entry; |
| 232 // TODO(nick): conflicting_new_folder_ids is a purposeless anachronism. | |
| 233 if (!pre_commit_id.ServerKnows() && local_entry.Get(IS_DIR)) { | |
| 234 conflicting_new_folder_ids->insert(pre_commit_id); | |
| 235 } | |
| 236 return response; | 248 return response; |
| 237 } | 249 } |
| 238 if (CommitResponse::RETRY == response) { | 250 if (CommitResponse::RETRY == response) { |
| 239 DVLOG(1) << "Retry Committing: " << local_entry; | 251 DVLOG(1) << "Retry Committing: " << local_entry; |
| 240 return response; | 252 return response; |
| 241 } | 253 } |
| 242 if (CommitResponse::OVER_QUOTA == response) { | 254 if (CommitResponse::OVER_QUOTA == response) { |
| 243 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; | 255 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; |
| 244 return response; | 256 return response; |
| 245 } | 257 } |
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 472 // Make a note of any deleted folders, whose children would have | 484 // Make a note of any deleted folders, whose children would have |
| 473 // been recursively deleted. | 485 // been recursively deleted. |
| 474 // TODO(nick): Here, commit_message.deleted() would be more correct than | 486 // TODO(nick): Here, commit_message.deleted() would be more correct than |
| 475 // local_entry->Get(IS_DEL). For example, an item could be renamed, and then | 487 // local_entry->Get(IS_DEL). For example, an item could be renamed, and then |
| 476 // deleted during the commit of the rename. Unit test & fix. | 488 // deleted during the commit of the rename. Unit test & fix. |
| 477 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) { | 489 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) { |
| 478 deleted_folders->insert(local_entry->Get(ID)); | 490 deleted_folders->insert(local_entry->Get(ID)); |
| 479 } | 491 } |
| 480 } | 492 } |
| 481 | 493 |
| 494 void ProcessCommitResponseCommand::ClearSyncingBits( | |
| 495 syncable::Directory *dir, | |
| 496 const vector<syncable::Id>& commit_ids) { | |
| 497 // This is part of the cleanup in the case of a failed commit. Normally we | |
| 498 // would unset the SYNCING bit when processing the commit response. In the | |
| 499 // failure case we don't process the response, so we need to clear those bits | |
| 500 // here. | |
| 501 syncable::WriteTransaction trans(FROM_HERE, syncable::SYNCER, dir); | |
| 502 for (size_t i = 0; i < commit_ids.size(); i++) { | |
| 503 syncable::MutableEntry entry(&trans, syncable::GET_BY_ID, commit_ids[i]); | |
| 504 if (entry.good()) { | |
| 505 entry.Put(syncable::SYNCING, false); | |
| 506 } else { | |
| 507 LOG(WARNING) << "Id: " << commit_ids[i] << " disappeared"; | |
| 508 } | |
| 509 } | |
| 510 } | |
| 511 | |
| 482 } // namespace browser_sync | 512 } // namespace browser_sync |
| OLD | NEW |