| 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().HasBookmarkCommitActivity()) { |
| 112 // If the commit failed, return the data to the ExtensionsActivityMonitor. |
| 113 if (session->status_controller().syncer_status() |
| 104 .num_successful_bookmark_commits == 0) { | 114 .num_successful_bookmark_commits == 0) { |
| 105 monitor->PutRecords(session->extensions_activity()); | 115 monitor->PutRecords(session->extensions_activity()); |
| 116 } |
| 117 // Clear our cached data in either case. |
| 106 session->mutable_extensions_activity()->clear(); | 118 session->mutable_extensions_activity()->clear(); |
| 107 } | 119 } |
| 120 |
| 108 return result; | 121 return result; |
| 109 } | 122 } |
| 110 | 123 |
| 111 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse( | 124 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse( |
| 112 SyncSession* session) { | 125 SyncSession* session) { |
| 113 syncable::Directory* dir = session->context()->directory(); | 126 syncable::Directory* dir = session->context()->directory(); |
| 127 StatusController* status = session->mutable_status_controller(); |
| 128 const CommitResponse& cr = commit_response_.commit(); |
| 129 const sync_pb::CommitMessage& commit_message = commit_message_.commit(); |
| 114 | 130 |
| 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; | 131 int transient_error_commits = 0; |
| 126 int conflicting_commits = 0; | 132 int conflicting_commits = 0; |
| 127 int error_commits = 0; | 133 int error_commits = 0; |
| 128 int successes = 0; | 134 int successes = 0; |
| 129 set<syncable::Id> conflicting_new_folder_ids; | 135 |
| 130 set<syncable::Id> deleted_folders; | 136 set<syncable::Id> deleted_folders; |
| 131 ConflictProgress* conflict_progress = status->mutable_conflict_progress(); | 137 ConflictProgress* conflict_progress = status->mutable_conflict_progress(); |
| 132 OrderedCommitSet::Projection proj = status->commit_id_projection(); | 138 OrderedCommitSet::Projection proj = status->commit_id_projection( |
| 139 commit_set_); |
| 140 |
| 133 if (!proj.empty()) { // Scope for WriteTransaction. | 141 if (!proj.empty()) { // Scope for WriteTransaction. |
| 134 WriteTransaction trans(FROM_HERE, SYNCER, dir); | 142 WriteTransaction trans(FROM_HERE, SYNCER, dir); |
| 135 for (size_t i = 0; i < proj.size(); i++) { | 143 for (size_t i = 0; i < proj.size(); i++) { |
| 136 CommitResponse::ResponseType response_type = | 144 CommitResponse::ResponseType response_type = ProcessSingleCommitResponse( |
| 137 ProcessSingleCommitResponse(&trans, cr.entryresponse(proj[i]), | 145 &trans, |
| 138 commit_message.entries(proj[i]), | 146 cr.entryresponse(proj[i]), |
| 139 status->GetCommitIdAt(proj[i]), | 147 commit_message.entries(proj[i]), |
| 140 &conflicting_new_folder_ids, | 148 commit_set_.GetCommitIdAt(proj[i]), |
| 141 &deleted_folders); | 149 &deleted_folders); |
| 142 switch (response_type) { | 150 switch (response_type) { |
| 143 case CommitResponse::INVALID_MESSAGE: | 151 case CommitResponse::INVALID_MESSAGE: |
| 144 ++error_commits; | 152 ++error_commits; |
| 145 break; | 153 break; |
| 146 case CommitResponse::CONFLICT: | 154 case CommitResponse::CONFLICT: |
| 147 ++conflicting_commits; | 155 ++conflicting_commits; |
| 148 // Only server CONFLICT responses will activate conflict resolution. | |
| 149 conflict_progress->AddServerConflictingItemById( | 156 conflict_progress->AddServerConflictingItemById( |
| 150 status->GetCommitIdAt(proj[i])); | 157 commit_set_.GetCommitIdAt(proj[i])); |
| 151 break; | 158 break; |
| 152 case CommitResponse::SUCCESS: | 159 case CommitResponse::SUCCESS: |
| 153 // TODO(sync): worry about sync_rate_ rate calc? | 160 // TODO(sync): worry about sync_rate_ rate calc? |
| 154 ++successes; | 161 ++successes; |
| 155 if (status->GetCommitModelTypeAt(proj[i]) == syncable::BOOKMARKS) | 162 if (commit_set_.GetModelTypeAt(proj[i]) == syncable::BOOKMARKS) |
| 156 status->increment_num_successful_bookmark_commits(); | 163 status->increment_num_successful_bookmark_commits(); |
| 157 status->increment_num_successful_commits(); | 164 status->increment_num_successful_commits(); |
| 158 break; | 165 break; |
| 159 case CommitResponse::OVER_QUOTA: | 166 case CommitResponse::OVER_QUOTA: |
| 160 // We handle over quota like a retry, which is same as transient. | 167 // We handle over quota like a retry, which is same as transient. |
| 161 case CommitResponse::RETRY: | 168 case CommitResponse::RETRY: |
| 162 case CommitResponse::TRANSIENT_ERROR: | 169 case CommitResponse::TRANSIENT_ERROR: |
| 163 ++transient_error_commits; | 170 ++transient_error_commits; |
| 164 break; | 171 break; |
| 165 default: | 172 default: |
| 166 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse"; | 173 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse"; |
| 167 } | 174 } |
| 168 } | 175 } |
| 169 } | 176 } |
| 170 | 177 |
| 171 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders); | 178 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders); |
| 172 | 179 |
| 173 int commit_count = static_cast<int>(proj.size()); | 180 int commit_count = static_cast<int>(proj.size()); |
| 174 if (commit_count == (successes + conflicting_commits)) { | 181 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; | 182 return SYNCER_OK; |
| 180 } else if (error_commits > 0) { | 183 } else if (error_commits > 0) { |
| 181 return SERVER_RETURN_UNKNOWN_ERROR; | 184 return SERVER_RETURN_UNKNOWN_ERROR; |
| 182 } else if (transient_error_commits > 0) { | 185 } else if (transient_error_commits > 0) { |
| 183 return SERVER_RETURN_TRANSIENT_ERROR; | 186 return SERVER_RETURN_TRANSIENT_ERROR; |
| 187 } else if (conflicting_commits > 0) { |
| 188 // This means that the server already has an item with this version, but |
| 189 // we haven't seen that update yet. |
| 190 // |
| 191 // A well-behaved client should respond to this by proceeding to the |
| 192 // download updates phase, fetching the conflicting items, then attempting |
| 193 // to resolve the conflict. That's not what this client does. |
| 194 // |
| 195 // We don't currently have any code to support that exceptional control |
| 196 // flow. We don't intend to add any because this response code will be |
| 197 // deprecated soon. Instead, we handle this in the same way that we handle |
| 198 // transient errors. We abort the current sync cycle, wait a little while, |
| 199 // then try again. The retry sync cycle will attempt to download updates |
| 200 // which should be sufficient to trigger client-side conflict resolution. |
| 201 // |
| 202 // Not treating this as an error would be dangerous. There's a risk that |
| 203 // the commit loop would loop indefinitely. The loop won't exit until the |
| 204 // number of unsynced items hits zero or an error is detected. If we're |
| 205 // constantly receiving conflict responses and we don't treat them as |
| 206 // errors, there would be no reason to leave that loop. |
| 207 // |
| 208 // TODO: Remove this option when the CONFLICT return value is fully |
| 209 // deprecated. |
| 210 return SERVER_RETURN_TRANSIENT_ERROR; |
| 184 } else { | 211 } else { |
| 185 LOG(FATAL) << "Inconsistent counts when processing commit response"; | 212 LOG(FATAL) << "Inconsistent counts when processing commit response"; |
| 186 return SYNCER_OK; | 213 return SYNCER_OK; |
| 187 } | 214 } |
| 188 } | 215 } |
| 189 | 216 |
| 190 void LogServerError(const CommitResponse_EntryResponse& res) { | 217 void LogServerError(const CommitResponse_EntryResponse& res) { |
| 191 if (res.has_error_message()) | 218 if (res.has_error_message()) |
| 192 LOG(WARNING) << " " << res.error_message(); | 219 LOG(WARNING) << " " << res.error_message(); |
| 193 else | 220 else |
| 194 LOG(WARNING) << " No detailed error message returned from server"; | 221 LOG(WARNING) << " No detailed error message returned from server"; |
| 195 } | 222 } |
| 196 | 223 |
| 197 CommitResponse::ResponseType | 224 CommitResponse::ResponseType |
| 198 ProcessCommitResponseCommand::ProcessSingleCommitResponse( | 225 ProcessCommitResponseCommand::ProcessSingleCommitResponse( |
| 199 syncable::WriteTransaction* trans, | 226 syncable::WriteTransaction* trans, |
| 200 const sync_pb::CommitResponse_EntryResponse& pb_server_entry, | 227 const sync_pb::CommitResponse_EntryResponse& pb_server_entry, |
| 201 const sync_pb::SyncEntity& commit_request_entry, | 228 const sync_pb::SyncEntity& commit_request_entry, |
| 202 const syncable::Id& pre_commit_id, | 229 const syncable::Id& pre_commit_id, |
| 203 std::set<syncable::Id>* conflicting_new_folder_ids, | |
| 204 set<syncable::Id>* deleted_folders) { | 230 set<syncable::Id>* deleted_folders) { |
| 205 | 231 |
| 206 const CommitResponse_EntryResponse& server_entry = | 232 const CommitResponse_EntryResponse& server_entry = |
| 207 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry); | 233 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry); |
| 208 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id); | 234 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id); |
| 209 CHECK(local_entry.good()); | 235 CHECK(local_entry.good()); |
| 210 bool syncing_was_set = local_entry.Get(SYNCING); | 236 bool syncing_was_set = local_entry.Get(SYNCING); |
| 211 local_entry.Put(SYNCING, false); | 237 local_entry.Put(SYNCING, false); |
| 212 | 238 |
| 213 CommitResponse::ResponseType response = (CommitResponse::ResponseType) | 239 CommitResponse::ResponseType response = (CommitResponse::ResponseType) |
| 214 server_entry.response_type(); | 240 server_entry.response_type(); |
| 215 if (!CommitResponse::ResponseType_IsValid(response)) { | 241 if (!CommitResponse::ResponseType_IsValid(response)) { |
| 216 LOG(ERROR) << "Commit response has unknown response type! Possibly out " | 242 LOG(ERROR) << "Commit response has unknown response type! Possibly out " |
| 217 "of date client?"; | 243 "of date client?"; |
| 218 return CommitResponse::INVALID_MESSAGE; | 244 return CommitResponse::INVALID_MESSAGE; |
| 219 } | 245 } |
| 220 if (CommitResponse::TRANSIENT_ERROR == response) { | 246 if (CommitResponse::TRANSIENT_ERROR == response) { |
| 221 DVLOG(1) << "Transient Error Committing: " << local_entry; | 247 DVLOG(1) << "Transient Error Committing: " << local_entry; |
| 222 LogServerError(server_entry); | 248 LogServerError(server_entry); |
| 223 return CommitResponse::TRANSIENT_ERROR; | 249 return CommitResponse::TRANSIENT_ERROR; |
| 224 } | 250 } |
| 225 if (CommitResponse::INVALID_MESSAGE == response) { | 251 if (CommitResponse::INVALID_MESSAGE == response) { |
| 226 LOG(ERROR) << "Error Commiting: " << local_entry; | 252 LOG(ERROR) << "Error Commiting: " << local_entry; |
| 227 LogServerError(server_entry); | 253 LogServerError(server_entry); |
| 228 return response; | 254 return response; |
| 229 } | 255 } |
| 230 if (CommitResponse::CONFLICT == response) { | 256 if (CommitResponse::CONFLICT == response) { |
| 231 DVLOG(1) << "Conflict Committing: " << local_entry; | 257 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; | 258 return response; |
| 237 } | 259 } |
| 238 if (CommitResponse::RETRY == response) { | 260 if (CommitResponse::RETRY == response) { |
| 239 DVLOG(1) << "Retry Committing: " << local_entry; | 261 DVLOG(1) << "Retry Committing: " << local_entry; |
| 240 return response; | 262 return response; |
| 241 } | 263 } |
| 242 if (CommitResponse::OVER_QUOTA == response) { | 264 if (CommitResponse::OVER_QUOTA == response) { |
| 243 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; | 265 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry; |
| 244 return response; | 266 return response; |
| 245 } | 267 } |
| (...skipping 225 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 471 // Make a note of any deleted folders, whose children would have | 493 // Make a note of any deleted folders, whose children would have |
| 472 // been recursively deleted. | 494 // been recursively deleted. |
| 473 // TODO(nick): Here, commit_message.deleted() would be more correct than | 495 // TODO(nick): Here, commit_message.deleted() would be more correct than |
| 474 // local_entry->Get(IS_DEL). For example, an item could be renamed, and then | 496 // local_entry->Get(IS_DEL). For example, an item could be renamed, and then |
| 475 // deleted during the commit of the rename. Unit test & fix. | 497 // deleted during the commit of the rename. Unit test & fix. |
| 476 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) { | 498 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) { |
| 477 deleted_folders->insert(local_entry->Get(ID)); | 499 deleted_folders->insert(local_entry->Get(ID)); |
| 478 } | 500 } |
| 479 } | 501 } |
| 480 | 502 |
| 503 void ProcessCommitResponseCommand::ClearSyncingBits( |
| 504 syncable::Directory *dir, |
| 505 const vector<syncable::Id>& commit_ids) { |
| 506 // This is part of the cleanup in the case of a failed commit. Normally we |
| 507 // would unset the SYNCING bit when processing the commit response. In the |
| 508 // failure case we don't process the response, so we need to clear those bits |
| 509 // here. |
| 510 syncable::WriteTransaction trans(FROM_HERE, syncable::SYNCER, dir); |
| 511 for (size_t i = 0; i < commit_ids.size(); i++) { |
| 512 syncable::MutableEntry entry(&trans, syncable::GET_BY_ID, commit_ids[i]); |
| 513 if (entry.good()) { |
| 514 entry.Put(syncable::SYNCING, false); |
| 515 } else { |
| 516 LOG(WARNING) << "Id: " << commit_ids[i] << " disappeared"; |
| 517 } |
| 518 } |
| 519 } |
| 520 |
| 481 } // namespace browser_sync | 521 } // namespace browser_sync |
| OLD | NEW |