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

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

Issue 25638003: sync: Implement per-type commit interface (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Attempt to fix win compile Created 7 years, 2 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 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 "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 "sync/engine/syncer_proto_util.h"
15 #include "sync/engine/syncer_util.h"
16 #include "sync/internal_api/public/base/unique_position.h"
17 #include "sync/sessions/sync_session.h"
18 #include "sync/syncable/entry.h"
19 #include "sync/syncable/model_neutral_mutable_entry.h"
20 #include "sync/syncable/syncable_model_neutral_write_transaction.h"
21 #include "sync/syncable/syncable_proto_util.h"
22 #include "sync/syncable/syncable_read_transaction.h"
23 #include "sync/syncable/syncable_util.h"
24 #include "sync/util/time.h"
25
26 using std::set;
27 using std::string;
28 using std::vector;
29 using sync_pb::CommitResponse;
30
31 namespace syncer {
32
33 using sessions::OrderedCommitSet;
34 using sessions::StatusController;
35 using sessions::SyncSession;
36 using syncable::ModelNeutralWriteTransaction;
37 using syncable::ModelNeutralMutableEntry;
38 using syncable::Entry;
39 using syncable::BASE_VERSION;
40 using syncable::GET_BY_ID;
41 using syncable::GET_BY_HANDLE;
42 using syncable::ID;
43 using syncable::IS_DEL;
44 using syncable::IS_DIR;
45 using syncable::IS_UNAPPLIED_UPDATE;
46 using syncable::IS_UNSYNCED;
47 using syncable::PARENT_ID;
48 using syncable::SERVER_IS_DEL;
49 using syncable::SERVER_PARENT_ID;
50 using syncable::SERVER_VERSION;
51 using syncable::SYNCER;
52 using syncable::SYNCING;
53
54 ProcessCommitResponseCommand::ProcessCommitResponseCommand(
55 const sessions::OrderedCommitSet& commit_set,
56 const sync_pb::ClientToServerMessage& commit_message,
57 const sync_pb::ClientToServerResponse& commit_response)
58 : commit_set_(commit_set),
59 commit_message_(commit_message),
60 commit_response_(commit_response) {
61 }
62
63 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {}
64
65 SyncerError ProcessCommitResponseCommand::ExecuteImpl(SyncSession* session) {
66 syncable::Directory* dir = session->context()->directory();
67 StatusController* status = session->mutable_status_controller();
68 const CommitResponse& cr = commit_response_.commit();
69 const sync_pb::CommitMessage& commit_message = commit_message_.commit();
70
71 int transient_error_commits = 0;
72 int conflicting_commits = 0;
73 int error_commits = 0;
74 int successes = 0;
75
76 set<syncable::Id> deleted_folders;
77
78 { // Scope for ModelNeutralWriteTransaction.
79 ModelNeutralWriteTransaction trans(FROM_HERE, SYNCER, dir);
80 for (size_t i = 0; i < commit_set_.Size(); i++) {
81 CommitResponse::ResponseType response_type = ProcessSingleCommitResponse(
82 &trans,
83 cr.entryresponse(i),
84 commit_message.entries(i),
85 commit_set_.GetCommitHandleAt(i),
86 &deleted_folders);
87 switch (response_type) {
88 case CommitResponse::INVALID_MESSAGE:
89 ++error_commits;
90 break;
91 case CommitResponse::CONFLICT:
92 ++conflicting_commits;
93 status->increment_num_server_conflicts();
94 break;
95 case CommitResponse::SUCCESS:
96 // TODO(sync): worry about sync_rate_ rate calc?
97 ++successes;
98 if (commit_set_.GetModelTypeAt(i) == BOOKMARKS)
99 status->increment_num_successful_bookmark_commits();
100 status->increment_num_successful_commits();
101 break;
102 case CommitResponse::OVER_QUOTA:
103 // We handle over quota like a retry, which is same as transient.
104 case CommitResponse::RETRY:
105 case CommitResponse::TRANSIENT_ERROR:
106 ++transient_error_commits;
107 break;
108 default:
109 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
110 }
111 }
112
113 MarkDeletedChildrenSynced(dir, &trans, &deleted_folders);
114 }
115
116 int commit_count = static_cast<int>(commit_set_.Size());
117 if (commit_count == successes) {
118 return SYNCER_OK;
119 } else if (error_commits > 0) {
120 return SERVER_RETURN_UNKNOWN_ERROR;
121 } else if (transient_error_commits > 0) {
122 return SERVER_RETURN_TRANSIENT_ERROR;
123 } else if (conflicting_commits > 0) {
124 // This means that the server already has an item with this version, but
125 // we haven't seen that update yet.
126 //
127 // A well-behaved client should respond to this by proceeding to the
128 // download updates phase, fetching the conflicting items, then attempting
129 // to resolve the conflict. That's not what this client does.
130 //
131 // We don't currently have any code to support that exceptional control
132 // flow. Instead, we abort the current sync cycle and start a new one. The
133 // end result is the same.
134 return SERVER_RETURN_CONFLICT;
135 } else {
136 LOG(FATAL) << "Inconsistent counts when processing commit response";
137 return SYNCER_OK;
138 }
139 }
140
141 void LogServerError(const sync_pb::CommitResponse_EntryResponse& res) {
142 if (res.has_error_message())
143 LOG(WARNING) << " " << res.error_message();
144 else
145 LOG(WARNING) << " No detailed error message returned from server";
146 }
147
148 CommitResponse::ResponseType
149 ProcessCommitResponseCommand::ProcessSingleCommitResponse(
150 syncable::ModelNeutralWriteTransaction* trans,
151 const sync_pb::CommitResponse_EntryResponse& server_entry,
152 const sync_pb::SyncEntity& commit_request_entry,
153 const int64 metahandle,
154 set<syncable::Id>* deleted_folders) {
155 ModelNeutralMutableEntry local_entry(trans, GET_BY_HANDLE, metahandle);
156 CHECK(local_entry.good());
157 bool syncing_was_set = local_entry.GetSyncing();
158 local_entry.PutSyncing(false);
159
160 CommitResponse::ResponseType response = (CommitResponse::ResponseType)
161 server_entry.response_type();
162 if (!CommitResponse::ResponseType_IsValid(response)) {
163 LOG(ERROR) << "Commit response has unknown response type! Possibly out "
164 "of date client?";
165 return CommitResponse::INVALID_MESSAGE;
166 }
167 if (CommitResponse::TRANSIENT_ERROR == response) {
168 DVLOG(1) << "Transient Error Committing: " << local_entry;
169 LogServerError(server_entry);
170 return CommitResponse::TRANSIENT_ERROR;
171 }
172 if (CommitResponse::INVALID_MESSAGE == response) {
173 LOG(ERROR) << "Error Commiting: " << local_entry;
174 LogServerError(server_entry);
175 return response;
176 }
177 if (CommitResponse::CONFLICT == response) {
178 DVLOG(1) << "Conflict Committing: " << local_entry;
179 return response;
180 }
181 if (CommitResponse::RETRY == response) {
182 DVLOG(1) << "Retry Committing: " << local_entry;
183 return response;
184 }
185 if (CommitResponse::OVER_QUOTA == response) {
186 LOG(WARNING) << "Hit deprecated OVER_QUOTA Committing: " << local_entry;
187 return response;
188 }
189 if (!server_entry.has_id_string()) {
190 LOG(ERROR) << "Commit response has no id";
191 return CommitResponse::INVALID_MESSAGE;
192 }
193
194 // Implied by the IsValid call above, but here for clarity.
195 DCHECK_EQ(CommitResponse::SUCCESS, response) << response;
196 // Check to see if we've been given the ID of an existing entry. If so treat
197 // it as an error response and retry later.
198 const syncable::Id& server_entry_id =
199 SyncableIdFromProto(server_entry.id_string());
200 if (local_entry.GetId() != server_entry_id) {
201 Entry e(trans, GET_BY_ID, server_entry_id);
202 if (e.good()) {
203 LOG(ERROR)
204 << "Got duplicate id when commiting id: "
205 << local_entry.GetId()
206 << ". Treating as an error return";
207 return CommitResponse::INVALID_MESSAGE;
208 }
209 }
210
211 if (server_entry.version() == 0) {
212 LOG(WARNING) << "Server returned a zero version on a commit response.";
213 }
214
215 ProcessSuccessfulCommitResponse(commit_request_entry, server_entry,
216 local_entry.GetId(), &local_entry, syncing_was_set, deleted_folders);
217 return response;
218 }
219
220 const string& ProcessCommitResponseCommand::GetResultingPostCommitName(
221 const sync_pb::SyncEntity& committed_entry,
222 const sync_pb::CommitResponse_EntryResponse& entry_response) {
223 const string& response_name =
224 SyncerProtoUtil::NameFromCommitEntryResponse(entry_response);
225 if (!response_name.empty())
226 return response_name;
227 return SyncerProtoUtil::NameFromSyncEntity(committed_entry);
228 }
229
230 bool ProcessCommitResponseCommand::UpdateVersionAfterCommit(
231 const sync_pb::SyncEntity& committed_entry,
232 const sync_pb::CommitResponse_EntryResponse& entry_response,
233 const syncable::Id& pre_commit_id,
234 syncable::ModelNeutralMutableEntry* local_entry) {
235 int64 old_version = local_entry->GetBaseVersion();
236 int64 new_version = entry_response.version();
237 bool bad_commit_version = false;
238 if (committed_entry.deleted() &&
239 !local_entry->GetUniqueClientTag().empty()) {
240 // If the item was deleted, and it's undeletable (uses the client tag),
241 // change the version back to zero. We must set the version to zero so
242 // that the server knows to re-create the item if it gets committed
243 // later for undeletion.
244 new_version = 0;
245 } else if (!pre_commit_id.ServerKnows()) {
246 bad_commit_version = 0 == new_version;
247 } else {
248 bad_commit_version = old_version > new_version;
249 }
250 if (bad_commit_version) {
251 LOG(ERROR) << "Bad version in commit return for " << *local_entry
252 << " new_id:" << SyncableIdFromProto(entry_response.id_string())
253 << " new_version:" << entry_response.version();
254 return false;
255 }
256
257 // Update the base version and server version. The base version must change
258 // here, even if syncing_was_set is false; that's because local changes were
259 // on top of the successfully committed version.
260 local_entry->PutBaseVersion(new_version);
261 DVLOG(1) << "Commit is changing base version of " << local_entry->GetId()
262 << " to: " << new_version;
263 local_entry->PutServerVersion(new_version);
264 return true;
265 }
266
267 bool ProcessCommitResponseCommand::ChangeIdAfterCommit(
268 const sync_pb::CommitResponse_EntryResponse& entry_response,
269 const syncable::Id& pre_commit_id,
270 syncable::ModelNeutralMutableEntry* local_entry) {
271 syncable::BaseWriteTransaction* trans = local_entry->base_write_transaction();
272 const syncable::Id& entry_response_id =
273 SyncableIdFromProto(entry_response.id_string());
274 if (entry_response_id != pre_commit_id) {
275 if (pre_commit_id.ServerKnows()) {
276 // The server can sometimes generate a new ID on commit; for example,
277 // when committing an undeletion.
278 DVLOG(1) << " ID changed while committing an old entry. "
279 << pre_commit_id << " became " << entry_response_id << ".";
280 }
281 ModelNeutralMutableEntry same_id(trans, GET_BY_ID, entry_response_id);
282 // We should trap this before this function.
283 if (same_id.good()) {
284 LOG(ERROR) << "ID clash with id " << entry_response_id
285 << " during commit " << same_id;
286 return false;
287 }
288 ChangeEntryIDAndUpdateChildren(trans, local_entry, entry_response_id);
289 DVLOG(1) << "Changing ID to " << entry_response_id;
290 }
291 return true;
292 }
293
294 void ProcessCommitResponseCommand::UpdateServerFieldsAfterCommit(
295 const sync_pb::SyncEntity& committed_entry,
296 const sync_pb::CommitResponse_EntryResponse& entry_response,
297 syncable::ModelNeutralMutableEntry* local_entry) {
298
299 // We just committed an entry successfully, and now we want to make our view
300 // of the server state consistent with the server state. We must be careful;
301 // |entry_response| and |committed_entry| have some identically named
302 // fields. We only want to consider fields from |committed_entry| when there
303 // is not an overriding field in the |entry_response|. We do not want to
304 // update the server data from the local data in the entry -- it's possible
305 // that the local data changed during the commit, and even if not, the server
306 // has the last word on the values of several properties.
307
308 local_entry->PutServerIsDel(committed_entry.deleted());
309 if (committed_entry.deleted()) {
310 // Don't clobber any other fields of deleted objects.
311 return;
312 }
313
314 local_entry->PutServerIsDir(
315 (committed_entry.folder() ||
316 committed_entry.bookmarkdata().bookmark_folder()));
317 local_entry->PutServerSpecifics(committed_entry.specifics());
318 local_entry->PutServerMtime(ProtoTimeToTime(committed_entry.mtime()));
319 local_entry->PutServerCtime(ProtoTimeToTime(committed_entry.ctime()));
320 if (committed_entry.has_unique_position()) {
321 local_entry->PutServerUniquePosition(
322 UniquePosition::FromProto(
323 committed_entry.unique_position()));
324 }
325
326 // TODO(nick): The server doesn't set entry_response.server_parent_id in
327 // practice; to update SERVER_PARENT_ID appropriately here we'd need to
328 // get the post-commit ID of the parent indicated by
329 // committed_entry.parent_id_string(). That should be inferrable from the
330 // information we have, but it's a bit convoluted to pull it out directly.
331 // Getting this right is important: SERVER_PARENT_ID gets fed back into
332 // old_parent_id during the next commit.
333 local_entry->PutServerParentId(local_entry->GetParentId());
334 local_entry->PutServerNonUniqueName(
335 GetResultingPostCommitName(committed_entry, entry_response));
336
337 if (local_entry->GetIsUnappliedUpdate()) {
338 // This shouldn't happen; an unapplied update shouldn't be committed, and
339 // if it were, the commit should have failed. But if it does happen: we've
340 // just overwritten the update info, so clear the flag.
341 local_entry->PutIsUnappliedUpdate(false);
342 }
343 }
344
345 void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
346 const sync_pb::SyncEntity& committed_entry,
347 const sync_pb::CommitResponse_EntryResponse& entry_response,
348 const syncable::Id& pre_commit_id,
349 syncable::ModelNeutralMutableEntry* local_entry,
350 bool syncing_was_set, set<syncable::Id>* deleted_folders) {
351 DCHECK(local_entry->GetIsUnsynced());
352
353 // Update SERVER_VERSION and BASE_VERSION.
354 if (!UpdateVersionAfterCommit(committed_entry, entry_response, pre_commit_id,
355 local_entry)) {
356 LOG(ERROR) << "Bad version in commit return for " << *local_entry
357 << " new_id:" << SyncableIdFromProto(entry_response.id_string())
358 << " new_version:" << entry_response.version();
359 return;
360 }
361
362 // If the server gave us a new ID, apply it.
363 if (!ChangeIdAfterCommit(entry_response, pre_commit_id, local_entry)) {
364 return;
365 }
366
367 // Update our stored copy of the server state.
368 UpdateServerFieldsAfterCommit(committed_entry, entry_response, local_entry);
369
370 // If the item doesn't need to be committed again (an item might need to be
371 // committed again if it changed locally during the commit), we can remove
372 // it from the unsynced list.
373 if (syncing_was_set) {
374 local_entry->PutIsUnsynced(false);
375 }
376
377 // Make a note of any deleted folders, whose children would have
378 // been recursively deleted.
379 // TODO(nick): Here, commit_message.deleted() would be more correct than
380 // local_entry->GetIsDel(). For example, an item could be renamed, and then
381 // deleted during the commit of the rename. Unit test & fix.
382 if (local_entry->GetIsDir() && local_entry->GetIsDel()) {
383 deleted_folders->insert(local_entry->GetId());
384 }
385 }
386
387 } // namespace syncer
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