Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(275)

Side by Side Diff: sync/engine/process_commit_response_command.cc

Issue 10210009: sync: Loop committing items without downloading updates (v2) (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebase Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
OLDNEW
« no previous file with comments | « sync/engine/process_commit_response_command.h ('k') | sync/engine/process_commit_response_command_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698