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

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

Issue 194065: Initial commit of sync engine code to browser/sync.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Fixes to gtest include path, reverted syncapi. Created 11 years, 3 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
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2006-2009 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 <set>
8 #include <vector>
9
10 #include "base/basictypes.h"
11 #include "chrome/browser/sync/engine/syncer_util.h"
idana 2009/09/10 05:44:37 syncer_util.h should be moved after syncer_status.
12 #include "chrome/browser/sync/engine/syncer_proto_util.h"
13 #include "chrome/browser/sync/engine/syncer_session.h"
14 #include "chrome/browser/sync/engine/syncer_status.h"
15 #include "chrome/browser/sync/engine/syncproto.h"
16 #include "chrome/browser/sync/syncable/directory_manager.h"
17 #include "chrome/browser/sync/syncable/syncable.h"
18 #include "chrome/browser/sync/util/character_set_converters.h"
19
20 using syncable::ScopedDirLookup;
21 using syncable::WriteTransaction;
22 using syncable::MutableEntry;
23 using syncable::Entry;
24 using syncable::Name;
25 using syncable::SyncName;
26 using syncable::DBName;
27
28 using std::set;
29 using std::vector;
30
31 using syncable::BASE_VERSION;
32 using syncable::GET_BY_ID;
33 using syncable::ID;
34 using syncable::IS_DEL;
35 using syncable::IS_DIR;
36 using syncable::IS_UNAPPLIED_UPDATE;
37 using syncable::IS_UNSYNCED;
38 using syncable::PARENT_ID;
39 using syncable::SERVER_PARENT_ID;
40 using syncable::SERVER_POSITION_IN_PARENT;
41 using syncable::SYNCER;
42 using syncable::SYNCING;
43
44 namespace browser_sync {
45
46 void IncrementErrorCounters(SyncerStatus status) {
47 status.increment_consecutive_problem_commits();
48 status.increment_consecutive_errors();
49 }
50 void ResetErrorCounters(SyncerStatus status) {
51 status.zero_consecutive_problem_commits();
52 status.zero_consecutive_errors();
53 }
54
55 ProcessCommitResponseCommand::ProcessCommitResponseCommand() {}
56 ProcessCommitResponseCommand::~ProcessCommitResponseCommand() {}
57
58 void ProcessCommitResponseCommand::ModelChangingExecuteImpl(
59 SyncerSession *session) {
60 // TODO(sync): This function returns if it sees problems. We probably want
61 // to flag the need for an update or similar.
62 ScopedDirLookup dir(session->dirman(), session->account_name());
63 if (!dir.good()) {
64 LOG(ERROR) << "Scoped dir lookup failed!";
65 return;
66 }
67 const ClientToServerResponse& response = session->commit_response();
68 const vector<syncable::Id>& commit_ids = session->commit_ids();
69
70 // TODO(sync): move counters out of here.
71 SyncerStatus status(session);
72
73 if (!response.has_commit()) {
74 // TODO(sync): What if we didn't try to commit anything?
75 LOG(WARNING) << "Commit response has no commit body!";
76 IncrementErrorCounters(status);
77 return;
78 }
79
80 const CommitResponse& cr = response.commit();
81 int commit_count = commit_ids.size();
82 if (cr.entryresponse_size() != commit_count) {
83 LOG(ERROR) << "Commit response has wrong number of entries! Expected:" <<
84 commit_count << " Got:" << cr.entryresponse_size();
85 for (int i = 0 ; i < cr.entryresponse_size() ; i++) {
86 LOG(ERROR) << "Response #" << i << " Value: " <<
87 cr.entryresponse(i).response_type();
88 if (cr.entryresponse(i).has_error_message())
89 LOG(ERROR) << " " << cr.entryresponse(i).error_message();
90 }
91 IncrementErrorCounters(status);
92 return;
93 }
94
95 // If we try to commit a parent and child together and the parent conflicts
96 // the child will have a bad parent causing an error. As this is not a
97 // critical error, we trap it and don't LOG(ERROR). To enable this we keep
98 // a map of conflicting new folders.
99 int transient_error_commits = 0;
100 int conflicting_commits = 0;
101 int error_commits = 0;
102 int successes = 0;
103 bool over_quota = false;
104 set<syncable::Id> conflicting_new_folder_ids;
105 set<syncable::Id> deleted_folders;
106 bool truncated_commit_logged = false;
107 { // Scope for WriteTransaction
108 WriteTransaction trans(dir, SYNCER, __FILE__, __LINE__);
109 for (int i = 0; i < cr.entryresponse_size(); i++) {
110 CommitResponse::RESPONSE_TYPE response_type =
111 ProcessSingleCommitResponse(&trans, cr.entryresponse(i),
112 commit_ids[i],
113 &conflicting_new_folder_ids,
114 &deleted_folders, session);
115 switch (response_type) {
116 case CommitResponse::INVALID_MESSAGE:
117 ++error_commits;
118 break;
119 case CommitResponse::CONFLICT:
120 ++conflicting_commits;
121 session->AddCommitConflict(commit_ids[i]);
122 break;
123 case CommitResponse::SUCCESS:
124 // TODO(sync): worry about sync_rate_ rate calc?
125 ++successes;
126 status.increment_successful_commits();
127 break;
128 case CommitResponse::OVER_QUOTA:
129 over_quota = true;
130 // We handle over quota like a retry.
131 case CommitResponse::RETRY:
132 session->AddBlockedItem(commit_ids[i]);
133 break;
134 case CommitResponse::TRANSIENT_ERROR:
135 ++transient_error_commits;
136 break;
137 default:
138 LOG(FATAL) << "Bad return from ProcessSingleCommitResponse";
139 }
140 }
141 }
142
143 // TODO(sync): move status reporting elsewhere.
144 status.set_conflicting_commits(conflicting_commits);
145 status.set_error_commits(error_commits);
146 if (0 == successes) {
147 status.increment_consecutive_transient_error_commits_by(
148 transient_error_commits);
149 status.increment_consecutive_errors_by(transient_error_commits);
150 } else {
151 status.zero_consecutive_transient_error_commits();
152 status.zero_consecutive_errors();
153 }
154 // If all commits are errors count it as an error.
155 if (commit_count == error_commits) {
156 // A larger error step than normal because a POST just succeeded.
157 status.TallyBigNewError();
158 }
159 if (commit_count != (conflicting_commits + error_commits +
160 transient_error_commits)) {
161 ResetErrorCounters(status);
162 }
163 SyncerUtil::MarkDeletedChildrenSynced(dir, &deleted_folders);
164 session->set_over_quota(over_quota);
165
166 return;
167 }
168
169 void LogServerError(const CommitResponse_EntryResponse& res) {
170 if (res.has_error_message())
171 LOG(ERROR) << " " << res.error_message();
172 else
173 LOG(ERROR) << " No detailed error message returned from server";
174 }
175
176 CommitResponse::RESPONSE_TYPE
177 ProcessCommitResponseCommand::ProcessSingleCommitResponse(
178 syncable::WriteTransaction* trans,
179 const sync_pb::CommitResponse_EntryResponse& pb_server_entry,
180 const syncable::Id& pre_commit_id,
181 std::set<syncable::Id>* conflicting_new_folder_ids,
182 set<syncable::Id>* deleted_folders,
183 SyncerSession* const session) {
184
185 const CommitResponse_EntryResponse& server_entry =
186 *static_cast<const CommitResponse_EntryResponse*>(&pb_server_entry);
187 MutableEntry local_entry(trans, GET_BY_ID, pre_commit_id);
188 CHECK(local_entry.good());
189 bool syncing_was_set = local_entry.Get(SYNCING);
190 local_entry.Put(SYNCING, false);
191
192 CommitResponse::RESPONSE_TYPE response = (CommitResponse::RESPONSE_TYPE)
193 server_entry.response_type();
194 if (!CommitResponse::RESPONSE_TYPE_IsValid(response)) {
195 LOG(ERROR) << "Commit response has unknown response type! Possibly out "
196 "of date client?";
197 return CommitResponse::INVALID_MESSAGE;
198 }
199 if (CommitResponse::TRANSIENT_ERROR == response) {
200 LOG(INFO) << "Transient Error Committing: " << local_entry;
201 LogServerError(server_entry);
202 return CommitResponse::TRANSIENT_ERROR;
203 }
204 if (CommitResponse::INVALID_MESSAGE == response) {
205 LOG(ERROR) << "Error Commiting: " << local_entry;
206 LogServerError(server_entry);
207 return response;
208 }
209 if (CommitResponse::CONFLICT == response) {
210 LOG(INFO) << "Conflict Committing: " << local_entry;
211 if (!pre_commit_id.ServerKnows() && local_entry.Get(IS_DIR)) {
212 conflicting_new_folder_ids->insert(pre_commit_id);
213 }
214 return response;
215 }
216 if (CommitResponse::RETRY == response) {
217 LOG(INFO) << "Retry Committing: " << local_entry;
218 return response;
219 }
220 if (CommitResponse::OVER_QUOTA == response) {
221 LOG(INFO) << "Hit Quota Committing: " << local_entry;
222 return response;
223 }
224 if (!server_entry.has_id_string()) {
225 LOG(ERROR) << "Commit response has no id";
226 return CommitResponse::INVALID_MESSAGE;
227 }
228
229 // implied by the IsValid call above, but here for clarity.
230 DCHECK_EQ(CommitResponse::SUCCESS, response) << response;
231 // Check to see if we've been given the ID of an existing entry. If so treat
232 // it as an error response and retry later.
233 if (pre_commit_id != server_entry.id()) {
234 Entry e(trans, GET_BY_ID, server_entry.id());
235 if (e.good()) {
236 LOG(ERROR) << "Got duplicate id when commiting id: " << pre_commit_id <<
237 ". Treating as an error return";
238 return CommitResponse::INVALID_MESSAGE;
239 }
240 }
241
242 if (server_entry.version() == 0) {
243 LOG(WARNING) << "Server returned a zero version on a commit response.";
244 }
245
246 ProcessSuccessfulCommitResponse(trans, server_entry, pre_commit_id,
247 &local_entry, syncing_was_set,
248 deleted_folders, session);
249 return response;
250 }
251
252 void ProcessCommitResponseCommand::ProcessSuccessfulCommitResponse(
253 syncable::WriteTransaction* trans,
254 const CommitResponse_EntryResponse& server_entry,
255 const syncable::Id& pre_commit_id, syncable::MutableEntry* local_entry,
256 bool syncing_was_set, set<syncable::Id>* deleted_folders,
257 SyncerSession* const session) {
258 int64 old_version = local_entry->Get(BASE_VERSION);
259 int64 new_version = server_entry.version();
260 bool bad_commit_version = false;
261 // TODO(sync): The !server_entry.has_id_string() clauses below were
262 // introduced when working with the new protocol.
263 if (!pre_commit_id.ServerKnows())
264 bad_commit_version = 0 == new_version;
265 else
266 bad_commit_version = old_version > new_version;
267 if (bad_commit_version) {
268 LOG(ERROR) << "Bad version in commit return for " << *local_entry <<
269 " new_id:" << server_entry.id() << " new_version:" <<
270 server_entry.version();
271 return;
272 }
273 if (server_entry.id() != pre_commit_id) {
274 if (pre_commit_id.ServerKnows()) {
275 // TODO(sync): In future it's possible that we'll want the opportunity
276 // to do a server triggered move aside here.
277 LOG(ERROR) << " ID change but not committing a new entry. " <<
278 pre_commit_id << " became " << server_entry.id() << ".";
279 return;
280 }
281 if (!server_entry.id().ServerKnows()) {
282 LOG(ERROR) << " New entries id < 0." << pre_commit_id << " became " <<
283 server_entry.id() << ".";
284 return;
285 }
286 MutableEntry same_id(trans, GET_BY_ID, server_entry.id());
287 // We should trap this before this function.
288 CHECK(!same_id.good()) << "ID clash with id " << server_entry.id() <<
289 " during commit " << same_id;
290 SyncerUtil::ChangeEntryIDAndUpdateChildren(
291 trans, local_entry, server_entry.id());
292 LOG(INFO) << "Changing ID to " << server_entry.id();
293 }
294
295 local_entry->Put(BASE_VERSION, new_version);
296 LOG(INFO) << "Commit is changing base version of " <<
297 local_entry->Get(ID) << " to: " << new_version;
298
299 if (local_entry->Get(IS_UNAPPLIED_UPDATE)) {
300 // This is possible, but very unlikely.
301 local_entry->Put(IS_UNAPPLIED_UPDATE, false);
302 }
303
304 if (server_entry.has_name()) {
305 if (syncing_was_set) {
306 PerformCommitTimeNameAside(trans, server_entry, local_entry);
307 } else {
308 // IS_UNSYNCED will ensure that this entry gets committed again,
309 // even if we skip this name aside. IS_UNSYNCED was probably previously
310 // set, but let's just set it anyway.
311 local_entry->Put(IS_UNSYNCED, true);
312 LOG(INFO) << "Skipping commit time name aside because" <<
313 " entry was changed during commit.";
314 }
315 }
316
317 if (syncing_was_set && server_entry.has_position_in_parent()) {
318 // The server has the final say on positioning, so apply the absolute
319 // position that it returns.
320 local_entry->Put(SERVER_POSITION_IN_PARENT,
321 server_entry.position_in_parent());
322
323 // We just committed successfully, so we assume that the position
324 // value we got applies to the PARENT_ID we submitted.
325 syncable::Id new_prev = SyncerUtil::ComputePrevIdFromServerPosition(
326 trans, local_entry, local_entry->Get(PARENT_ID));
327 if (!local_entry->PutPredecessor(new_prev)) {
328 LOG(WARNING) << "PutPredecessor failed after successful commit";
329 }
330 }
331
332 if (syncing_was_set) {
333 local_entry->Put(IS_UNSYNCED, false);
334 }
335 if (local_entry->Get(IS_DIR) && local_entry->Get(IS_DEL)) {
336 deleted_folders->insert(local_entry->Get(ID));
337 }
338 }
339
340 void ProcessCommitResponseCommand::PerformCommitTimeNameAside(
341 syncable::WriteTransaction* trans,
342 const CommitResponse_EntryResponse& server_entry,
343 syncable::MutableEntry* local_entry) {
344 Name old_name(local_entry->GetName());
345
346 // Ensure that we don't collide with an existing entry.
347 SyncName server_name =
348 SyncerProtoUtil::NameFromCommitEntryResponse(server_entry);
349
350 LOG(INFO) << "Server provided committed name:" << server_name.value();
351 if (!server_name.value().empty() &&
352 static_cast<SyncName&>(old_name) != server_name) {
353 LOG(INFO) << "Server name differs from local name, attempting"
354 << " commit time name aside.";
355
356 DBName db_name(server_name.value());
357 db_name.MakeOSLegal();
358
359 // This is going to produce ~1 names instead of (Edited) names.
360 // Since this should be EXTREMELY rare, we do this for now.
361 db_name.MakeNoncollidingForEntry(trans, local_entry->Get(SERVER_PARENT_ID),
362 local_entry);
363
364 CHECK(!db_name.empty());
365
366 LOG(INFO) << "Server commit moved aside entry: " << old_name.db_value()
367 << " to new name " << db_name;
368
369 // Should be safe since we're in a "commit lock."
370 local_entry->PutName(Name::FromDBNameAndSyncName(db_name, server_name));
371 }
372 }
373
374 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698