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

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

Issue 9699057: [Sync] Move 'sync' target to sync/ (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address Tim's comments Created 8 years, 9 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
(Empty)
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/sync/engine/process_commit_response_command.h"
6
7 #include <cstddef>
8 #include <set>
9 #include <string>
10 #include <vector>
11
12 #include "base/basictypes.h"
13 #include "base/location.h"
14 #include "chrome/browser/sync/engine/syncer_proto_util.h"
15 #include "chrome/browser/sync/engine/syncer_util.h"
16 #include "chrome/browser/sync/engine/syncproto.h"
17 #include "chrome/browser/sync/sessions/sync_session.h"
18 #include "chrome/browser/sync/syncable/syncable.h"
19 #include "chrome/browser/sync/util/time.h"
20
21 using syncable::WriteTransaction;
22 using syncable::MutableEntry;
23 using syncable::Entry;
24
25 using std::set;
26 using std::string;
27 using std::vector;
28
29 using syncable::BASE_VERSION;
30 using syncable::GET_BY_ID;
31 using syncable::ID;
32 using syncable::IS_DEL;
33 using syncable::IS_DIR;
34 using syncable::IS_UNAPPLIED_UPDATE;
35 using syncable::IS_UNSYNCED;
36 using syncable::PARENT_ID;
37 using syncable::SERVER_IS_DEL;
38 using syncable::SERVER_PARENT_ID;
39 using syncable::SERVER_POSITION_IN_PARENT;
40 using syncable::SERVER_VERSION;
41 using syncable::SYNCER;
42 using syncable::SYNCING;
43
44 namespace browser_sync {
45
46 using sessions::OrderedCommitSet;
47 using sessions::StatusController;
48 using sessions::SyncSession;
49 using sessions::ConflictProgress;
50
51 ProcessCommitResponseCommand::ProcessCommitResponseCommand() {}
52 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {}
53
54 std::set<ModelSafeGroup> ProcessCommitResponseCommand::GetGroupsToChange(
55 const sessions::SyncSession& session) const {
56 std::set<ModelSafeGroup> groups_with_commits;
57
58 syncable::Directory* dir = session.context()->directory();
59 syncable::ReadTransaction trans(FROM_HERE, dir);
60 const StatusController& status = session.status_controller();
61 for (size_t i = 0; i < status.commit_ids().size(); ++i) {
62 groups_with_commits.insert(
63 GetGroupForModelType(status.GetUnrestrictedCommitModelTypeAt(i),
64 session.routing_info()));
65 }
66
67 return groups_with_commits;
68 }
69
70 SyncerError ProcessCommitResponseCommand::ModelNeutralExecuteImpl(
71 sessions::SyncSession* session) {
72 const StatusController& status = session->status_controller();
73 const ClientToServerResponse& response(status.commit_response());
74 const vector<syncable::Id>& commit_ids(status.commit_ids());
75
76 if (!response.has_commit()) {
77 // TODO(sync): What if we didn't try to commit anything?
78 LOG(WARNING) << "Commit response has no commit body!";
79 return SERVER_RESPONSE_VALIDATION_FAILED;
80 }
81
82 const CommitResponse& cr = response.commit();
83 int commit_count = commit_ids.size();
84 if (cr.entryresponse_size() != commit_count) {
85 LOG(ERROR) << "Commit response has wrong number of entries! Expected:" <<
86 commit_count << " Got:" << cr.entryresponse_size();
87 for (int i = 0 ; i < cr.entryresponse_size() ; i++) {
88 LOG(ERROR) << "Response #" << i << " Value: " <<
89 cr.entryresponse(i).response_type();
90 if (cr.entryresponse(i).has_error_message())
91 LOG(ERROR) << " " << cr.entryresponse(i).error_message();
92 }
93 return SERVER_RESPONSE_VALIDATION_FAILED;
94 }
95 return SYNCER_OK;
96 }
97
98 SyncerError ProcessCommitResponseCommand::ModelChangingExecuteImpl(
99 SyncSession* session) {
100 SyncerError result = ProcessCommitResponse(session);
101 ExtensionsActivityMonitor* monitor = session->context()->extensions_monitor();
102 if (session->status_controller().HasBookmarkCommitActivity() &&
103 session->status_controller().syncer_status()
104 .num_successful_bookmark_commits == 0) {
105 monitor->PutRecords(session->extensions_activity());
106 session->mutable_extensions_activity()->clear();
107 }
108 return result;
109 }
110
111 SyncerError ProcessCommitResponseCommand::ProcessCommitResponse(
112 SyncSession* session) {
113 syncable::Directory* dir = session->context()->directory();
114
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;
126 int conflicting_commits = 0;
127 int error_commits = 0;
128 int successes = 0;
129 set<syncable::Id> conflicting_new_folder_ids;
130 set<syncable::Id> deleted_folders;
131 ConflictProgress* conflict_progress = status->mutable_conflict_progress();
132 OrderedCommitSet::Projection proj = status->commit_id_projection();
133 if (!proj.empty()) { // Scope for WriteTransaction.
134 WriteTransaction trans(FROM_HERE, SYNCER, dir);
135 for (size_t i = 0; i < proj.size(); i++) {
136 CommitResponse::ResponseType response_type =
137 ProcessSingleCommitResponse(&trans, cr.entryresponse(proj[i]),
138 commit_message.entries(proj[i]),
139 status->GetCommitIdAt(proj[i]),
140 &conflicting_new_folder_ids,
141 &deleted_folders);
142 switch (response_type) {
143 case CommitResponse::INVALID_MESSAGE:
144 ++error_commits;
145 break;
146 case CommitResponse::CONFLICT:
147 ++conflicting_commits;
148 // Only server CONFLICT responses will activate conflict resolution.
149 conflict_progress->AddServerConflictingItemById(
150 status->GetCommitIdAt(proj[i]));
151 break;
152 case CommitResponse::SUCCESS:
153 // TODO(sync): worry about sync_rate_ rate calc?
154 ++successes;
155 if (status->GetCommitModelTypeAt(proj[i]) == syncable::BOOKMARKS)
156 status->increment_num_successful_bookmark_commits();
157 status->increment_num_successful_commits();
158 break;
159 case CommitResponse::OVER_QUOTA:
160 // We handle over quota like a retry, which is same as transient.
161 case CommitResponse::RETRY:
162 case CommitResponse::TRANSIENT_ERROR:
163 ++transient_error_commits;
164 break;
165 default:
166 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
167 }
168 }
169 }
170
171 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders);
172
173 int commit_count = static_cast<int>(proj.size());
174 if (commit_count == (successes + conflicting_commits)) {
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;
180 } else if (error_commits > 0) {
181 return SERVER_RETURN_UNKNOWN_ERROR;
182 } else if (transient_error_commits > 0) {
183 return SERVER_RETURN_TRANSIENT_ERROR;
184 } else {
185 LOG(FATAL) << "Inconsistent counts when processing commit response";
186 return SYNCER_OK;
187 }
188 }
189
190 void LogServerError(const CommitResponse_EntryResponse& res) {
191 if (res.has_error_message())
192 LOG(WARNING) << " " << res.error_message();
193 else
194 LOG(WARNING) << " No detailed error message returned from server";
195 }
196
197 CommitResponse::ResponseType
198 ProcessCommitResponseCommand::ProcessSingleCommitResponse(
199 syncable::WriteTransaction* trans,
200 const sync_pb::CommitResponse_EntryResponse& pb_server_entry,
201 const sync_pb::SyncEntity& commit_request_entry,
202 const syncable::Id& pre_commit_id,
203 std::set<syncable::Id>* conflicting_new_folder_ids,
204 set<syncable::Id>* deleted_folders) {
205
206 const CommitResponse_EntryResponse& server_entry =
207 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry);
208 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id);
209 CHECK(local_entry.good());
210 bool syncing_was_set = local_entry.Get(SYNCING);
211 local_entry.Put(SYNCING, false);
212
213 CommitResponse::ResponseType response = (CommitResponse::ResponseType)
214 server_entry.response_type();
215 if (!CommitResponse::ResponseType_IsValid(response)) {
216 LOG(ERROR) << "Commit response has unknown response type! Possibly out "
217 "of date client?";
218 return CommitResponse::INVALID_MESSAGE;
219 }
220 if (CommitResponse::TRANSIENT_ERROR == response) {
221 DVLOG(1) << "Transient Error Committing: " << local_entry;
222 LogServerError(server_entry);
223 return CommitResponse::TRANSIENT_ERROR;
224 }
225 if (CommitResponse::INVALID_MESSAGE == response) {
226 LOG(ERROR) << "Error Commiting: " << local_entry;
227 LogServerError(server_entry);
228 return response;
229 }
230 if (CommitResponse::CONFLICT == response) {
231 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;
237 }
238 if (CommitResponse::RETRY == response) {
239 DVLOG(1) << "Retry Committing: " << local_entry;
240 return response;
241 }
242 if (CommitResponse::OVER_QUOTA == response) {
243 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
244 return response;
245 }
246 if (!server_entry.has_id_string()) {
247 LOG(ERROR) << "Commit response has no id";
248 return CommitResponse::INVALID_MESSAGE;
249 }
250
251 // Implied by the IsValid call above, but here for clarity.
252 DCHECK_EQ(CommitResponse::SUCCESS, response) << response;
253 // Check to see if we've been given the ID of an existing entry. If so treat
254 // it as an error response and retry later.
255 if (pre_commit_id != server_entry.id()) {
256 Entry e(trans, GET_BY_ID, server_entry.id());
257 if (e.good()) {
258 LOG(ERROR) << "Got duplicate id when commiting id: " << pre_commit_id <<
259 ". Treating as an error return";
260 return CommitResponse::INVALID_MESSAGE;
261 }
262 }
263
264 if (server_entry.version() == 0) {
265 LOG(WARNING) << "Server returned a zero version on a commit response.";
266 }
267
268 ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
269 pre_commit_id, &local_entry, syncing_was_set, deleted_folders);
270 return response;
271 }
272
273 const string& ProcessCommitResponseCommand::GetResultingPostCommitName(
274 const sync_pb::SyncEntity& committed_entry,
275 const CommitResponse_EntryResponse& entry_response) {
276 const string& response_name =
277 SyncerProtoUtil::NameFromCommitEntryResponse(entry_response);
278 if (!response_name.empty())
279 return response_name;
280 return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
281 }
282
283 bool ProcessCommitResponseCommand::UpdateVersionAfterCommit(
284 const sync_pb::SyncEntity& committed_entry,
285 const CommitResponse_EntryResponse& entry_response,
286 const syncable::Id& pre_commit_id,
287 syncable::MutableEntry* local_entry) {
288 int64 old_version = local_entry->Get(BASE_VERSION);
289 int64 new_version = entry_response.version();
290 bool bad_commit_version = false;
291 if (committed_entry.deleted() &&
292 !local_entry->Get(syncable::UNIQUE_CLIENT_TAG).empty()) {
293 // If the item was deleted, and it's undeletable (uses the client tag),
294 // change the version back to zero. We must set the version to zero so
295 // that the server knows to re-create the item if it gets committed
296 // later for undeletion.
297 new_version = 0;
298 } else if (!pre_commit_id.ServerKnows()) {
299 bad_commit_version = 0 == new_version;
300 } else {
301 bad_commit_version = old_version > new_version;
302 }
303 if (bad_commit_version) {
304 LOG(ERROR) << "Bad version in commit return for " << *local_entry
305 << " new_id:" << entry_response.id() << " new_version:"
306 << entry_response.version();
307 return false;
308 }
309
310 // Update the base version and server version. The base version must change
311 // here, even if syncing_was_set is false; that's because local changes were
312 // on top of the successfully committed version.
313 local_entry->Put(BASE_VERSION, new_version);
314 DVLOG(1) << "Commit is changing base version of " << local_entry->Get(ID)
315 << " to: " << new_version;
316 local_entry->Put(SERVER_VERSION, new_version);
317 return true;
318 }
319
320 bool ProcessCommitResponseCommand::ChangeIdAfterCommit(
321 const CommitResponse_EntryResponse& entry_response,
322 const syncable::Id& pre_commit_id,
323 syncable::MutableEntry* local_entry) {
324 syncable::WriteTransaction* trans = local_entry->write_transaction();
325 if (entry_response.id() != pre_commit_id) {
326 if (pre_commit_id.ServerKnows()) {
327 // The server can sometimes generate a new ID on commit; for example,
328 // when committing an undeletion.
329 DVLOG(1) << " ID changed while committing an old entry. "
330 << pre_commit_id << " became " << entry_response.id() << ".";
331 }
332 MutableEntry same_id(trans, GET_BY_ID, entry_response.id());
333 // We should trap this before this function.
334 if (same_id.good()) {
335 LOG(ERROR) << "ID clash with id " << entry_response.id()
336 << " during commit " << same_id;
337 return false;
338 }
339 SyncerUtil::ChangeEntryIDAndUpdateChildren(
340 trans, local_entry, entry_response.id());
341 DVLOG(1) << "Changing ID to " << entry_response.id();
342 }
343 return true;
344 }
345
346 void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit(
347 const sync_pb::SyncEntity& committed_entry,
348 const CommitResponse_EntryResponse& entry_response,
349 syncable::MutableEntry* local_entry) {
350
351 // We just committed an entry successfully, and now we want to make our view
352 // of the server state consistent with the server state. We must be careful;
353 // |entry_response| and |committed_entry| have some identically named
354 // fields. We only want to consider fields from |committed_entry| when there
355 // is not an overriding field in the |entry_response|. We do not want to
356 // update the server data from the local data in the entry -- it's possible
357 // that the local data changed during the commit, and even if not, the server
358 // has the last word on the values of several properties.
359
360 local_entry->Put(SERVER_IS_DEL, committed_entry.deleted());
361 if (committed_entry.deleted()) {
362 // Don't clobber any other fields of deleted objects.
363 return;
364 }
365
366 local_entry->Put(syncable::SERVER_IS_DIR,
367 (committed_entry.folder() ||
368 committed_entry.bookmarkdata().bookmark_folder()));
369 local_entry->Put(syncable::SERVER_SPECIFICS,
370 committed_entry.specifics());
371 local_entry->Put(syncable::SERVER_MTIME,
372 ProtoTimeToTime(committed_entry.mtime()));
373 local_entry->Put(syncable::SERVER_CTIME,
374 ProtoTimeToTime(committed_entry.ctime()));
375 local_entry->Put(syncable::SERVER_POSITION_IN_PARENT,
376 entry_response.position_in_parent());
377 // TODO(nick): The server doesn't set entry_response.server_parent_id in
378 // practice; to update SERVER_PARENT_ID appropriately here we'd need to
379 // get the post-commit ID of the parent indicated by
380 // committed_entry.parent_id_string(). That should be inferrable from the
381 // information we have, but it's a bit convoluted to pull it out directly.
382 // Getting this right is important: SERVER_PARENT_ID gets fed back into
383 // old_parent_id during the next commit.
384 local_entry->Put(syncable::SERVER_PARENT_ID,
385 local_entry->Get(syncable::PARENT_ID));
386 local_entry->Put(syncable::SERVER_NON_UNIQUE_NAME,
387 GetResultingPostCommitName(committed_entry, entry_response));
388
389 if (local_entry->Get(IS_UNAPPLIED_UPDATE)) {
390 // This shouldn't happen; an unapplied update shouldn't be committed, and
391 // if it were, the commit should have failed. But if it does happen: we've
392 // just overwritten the update info, so clear the flag.
393 local_entry->Put(IS_UNAPPLIED_UPDATE, false);
394 }
395 }
396
397 void ProcessCommitResponseCommand::OverrideClientFieldsAfterCommit(
398 const sync_pb::SyncEntity& committed_entry,
399 const CommitResponse_EntryResponse& entry_response,
400 syncable::MutableEntry* local_entry) {
401 if (committed_entry.deleted()) {
402 // If an entry's been deleted, nothing else matters.
403 DCHECK(local_entry->Get(IS_DEL));
404 return;
405 }
406
407 // Update the name.
408 const string& server_name =
409 GetResultingPostCommitName(committed_entry, entry_response);
410 const string& old_name =
411 local_entry->Get(syncable::NON_UNIQUE_NAME);
412
413 if (!server_name.empty() && old_name != server_name) {
414 DVLOG(1) << "During commit, server changed name: " << old_name
415 << " to new name: " << server_name;
416 local_entry->Put(syncable::NON_UNIQUE_NAME, server_name);
417 }
418
419 // The server has the final say on positioning, so apply the absolute
420 // position that it returns.
421 if (entry_response.has_position_in_parent()) {
422 // The SERVER_ field should already have been written.
423 DCHECK_EQ(entry_response.position_in_parent(),
424 local_entry->Get(SERVER_POSITION_IN_PARENT));
425
426 // We just committed successfully, so we assume that the position
427 // value we got applies to the PARENT_ID we submitted.
428 syncable::Id new_prev = local_entry->ComputePrevIdFromServerPosition(
429 local_entry->Get(PARENT_ID));
430 if (!local_entry->PutPredecessor(new_prev)) {
431 // TODO(lipalani) : Propagate the error to caller. crbug.com/100444.
432 NOTREACHED();
433 }
434 }
435 }
436
437 void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
438 const sync_pb::SyncEntity& committed_entry,
439 const CommitResponse_EntryResponse& entry_response,
440 const syncable::Id& pre_commit_id, syncable::MutableEntry* local_entry,
441 bool syncing_was_set, set<syncable::Id>* deleted_folders) {
442 DCHECK(local_entry->Get(IS_UNSYNCED));
443
444 // Update SERVER_VERSION and BASE_VERSION.
445 if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id,
446 local_entry)) {
447 LOG(ERROR) << "Bad version in commit return for " << *local_entry
448 << " new_id:" << entry_response.id() << " new_version:"
449 << entry_response.version();
450 return;
451 }
452
453 // If the server gave us a new ID, apply it.
454 if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) {
455 return;
456 }
457
458 // Update our stored copy of the server state.
459 UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry);
460
461 // If the item doesn't need to be committed again (an item might need to be
462 // committed again if it changed locally during the commit), we can remove
463 // it from the unsynced list. Also, we should change the locally-
464 // visible properties to apply any canonicalizations or fixups
465 // that the server introduced during the commit.
466 if (syncing_was_set) {
467 OverrideClientFieldsAfterCommit(committed_entry, entry_response,
468 local_entry);
469 local_entry->Put(IS_UNSYNCED, false);
470 }
471
472 // Make a note of any deleted folders, whose children would have
473 // been recursively deleted.
474 // 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
476 // deleted during the commit of the rename. Unit test & fix.
477 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) {
478 deleted_folders->insert(local_entry->Get(ID));
479 }
480 }
481
482 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698