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

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

Issue 6874018: make new syncer thread the default. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Send for CR. Created 9 years, 8 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) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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 "chrome/browser/sync/engine/syncer_thread.h" 5 #include "chrome/browser/sync/engine/syncer_thread.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <queue>
9 #include <string>
10 #include <vector>
11 8
12 #include "base/rand_util.h" 9 #include "base/rand_util.h"
13 #include "base/third_party/dynamic_annotations/dynamic_annotations.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/sync/engine/model_safe_worker.h"
16 #include "chrome/browser/sync/engine/net/server_connection_manager.h"
17 #include "chrome/browser/sync/engine/syncer.h" 10 #include "chrome/browser/sync/engine/syncer.h"
18 #include "chrome/browser/sync/sessions/sync_session.h"
19 11
20 #if defined(OS_MACOSX)
21 #include <CoreFoundation/CFNumber.h>
22 #include <IOKit/IOTypes.h>
23 #include <IOKit/IOKitLib.h>
24 #endif
25
26 using std::priority_queue;
27 using std::min;
28 using base::Time;
29 using base::TimeDelta; 12 using base::TimeDelta;
30 using base::TimeTicks; 13 using base::TimeTicks;
31 14
32 namespace browser_sync { 15 namespace browser_sync {
33 16
34 using sessions::SyncSession; 17 using sessions::SyncSession;
35 using sessions::SyncSessionSnapshot; 18 using sessions::SyncSessionSnapshot;
36 using sessions::SyncSourceInfo; 19 using sessions::SyncSourceInfo;
37 using syncable::ModelTypePayloadMap; 20 using syncable::ModelTypePayloadMap;
38 21 using syncable::ModelTypeBitSet;
39 // We use high values here to ensure that failure to receive poll updates from 22 using sync_pb::GetUpdatesCallerInfo;
40 // the server doesn't result in rapid-fire polling from the client due to low 23
41 // local limits. 24 SyncerThread::DelayProvider::DelayProvider() {}
42 const int SyncerThread::kDefaultShortPollIntervalSeconds = 3600 * 8; 25 SyncerThread::DelayProvider::~DelayProvider() {}
43 const int SyncerThread::kDefaultLongPollIntervalSeconds = 3600 * 12; 26
44 27 SyncerThread::WaitInterval::WaitInterval() {}
45 // TODO(tim): This is used to regulate the short poll (when notifications are 28 SyncerThread::WaitInterval::~WaitInterval() {}
46 // disabled) based on user idle time. If it is set to a smaller value than 29
47 // the short poll interval, it basically does nothing; for now, this is what 30 SyncerThread::SyncSessionJob::SyncSessionJob() {}
48 // we want and allows stronger control over the poll rate from the server. We 31 SyncerThread::SyncSessionJob::~SyncSessionJob() {}
49 // should probably re-visit this code later and figure out if user idle time 32
50 // is really something we want and make sure it works, if it is. 33 SyncerThread::SyncSessionJob::SyncSessionJob(SyncSessionJobPurpose purpose,
51 const int SyncerThread::kDefaultMaxPollIntervalMs = 30 * 60 * 1000; 34 base::TimeTicks start,
52 35 linked_ptr<sessions::SyncSession> session, bool is_canary_job,
53 // Backoff interval randomization factor. 36 const tracked_objects::Location& nudge_location) : purpose(purpose),
54 static const int kBackoffRandomizationFactor = 2; 37 scheduled_start(start),
55 38 session(session),
56 const int SyncerThread::kMaxBackoffSeconds = 60 * 60 * 4; // 4 hours. 39 is_canary_job(is_canary_job),
57 40 nudge_location(nudge_location) {
58 SyncerThread::ProtectedFields::ProtectedFields() 41 }
59 : stop_syncer_thread_(false), 42
60 pause_requested_(false), 43 TimeDelta SyncerThread::DelayProvider::GetDelay(
61 paused_(false), 44 const base::TimeDelta& last_delay) {
62 syncer_(NULL), 45 return SyncerThread::GetRecommendedDelay(last_delay);
63 connected_(false), 46 }
64 pending_nudge_source_(kUnknown) {} 47
65 48 GetUpdatesCallerInfo::GetUpdatesSource GetUpdatesFromNudgeSource(
66 SyncerThread::ProtectedFields::~ProtectedFields() {} 49 NudgeSource source) {
67 50 switch (source) {
68 void SyncerThread::NudgeSyncerWithPayloads( 51 case NUDGE_SOURCE_NOTIFICATION:
69 int milliseconds_from_now, 52 return GetUpdatesCallerInfo::NOTIFICATION;
70 NudgeSource source, 53 case NUDGE_SOURCE_LOCAL:
71 const ModelTypePayloadMap& model_types_with_payloads) { 54 return GetUpdatesCallerInfo::LOCAL;
72 base::AutoLock lock(lock_); 55 case NUDGE_SOURCE_CONTINUATION:
73 if (vault_.syncer_ == NULL) { 56 return GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION;
57 case NUDGE_SOURCE_UNKNOWN:
58 return GetUpdatesCallerInfo::UNKNOWN;
59 default:
60 NOTREACHED();
61 return GetUpdatesCallerInfo::UNKNOWN;
62 }
63 }
64
65 SyncerThread::WaitInterval::WaitInterval(Mode mode, TimeDelta length)
66 : mode(mode), had_nudge(false), length(length) { }
67
68 SyncerThread::SyncerThread(sessions::SyncSessionContext* context,
69 Syncer* syncer)
70 : thread_("SyncEngine_SyncerThread"),
71 syncer_short_poll_interval_seconds_(
72 TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds)),
73 syncer_long_poll_interval_seconds_(
74 TimeDelta::FromSeconds(kDefaultLongPollIntervalSeconds)),
75 mode_(NORMAL_MODE),
76 server_connection_ok_(false),
77 delay_provider_(new DelayProvider()),
78 syncer_(syncer),
79 session_context_(context) {
80 }
81
82 SyncerThread::~SyncerThread() {
83 DCHECK(!thread_.IsRunning());
84 }
85
86 void SyncerThread::CheckServerConnectionManagerStatus(
87 HttpResponse::ServerConnectionCode code) {
88
89 VLOG(2) << "SyncerThread(" << this << ")" << " Server connection changed."
90 << "Old mode: " << server_connection_ok_ << " Code: " << code;
91 // Note, be careful when adding cases here because if the SyncerThread
92 // thinks there is no valid connection as determined by this method, it
93 // will drop out of *all* forward progress sync loops (it won't poll and it
94 // will queue up Talk notifications but not actually call SyncShare) until
95 // some external action causes a ServerConnectionManager to broadcast that
96 // a valid connection has been re-established.
97 if (HttpResponse::CONNECTION_UNAVAILABLE == code ||
98 HttpResponse::SYNC_AUTH_ERROR == code) {
99 server_connection_ok_ = false;
100 VLOG(2) << "SyncerThread(" << this << ")" << " Server connection changed."
101 << " new mode:" << server_connection_ok_;
102 } else if (HttpResponse::SERVER_CONNECTION_OK == code) {
103 server_connection_ok_ = true;
104 VLOG(2) << "SyncerThread(" << this << ")" << " Server connection changed."
105 << " new mode:" << server_connection_ok_;
106 DoCanaryJob();
107 }
108 }
109
110 void SyncerThread::Start(Mode mode, ModeChangeCallback* callback) {
111 VLOG(2) << "SyncerThread(" << this << ")" << " Start called from thread "
112 << MessageLoop::current()->thread_name();
113 if (!thread_.IsRunning()) {
114 VLOG(2) << "SyncerThread(" << this << ")" << " Starting thread with mode "
115 << mode;
116 if (!thread_.Start()) {
117 NOTREACHED() << "Unable to start SyncerThread.";
118 return;
119 }
120 WatchConnectionManager();
121 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
122 this, &SyncerThread::SendInitialSnapshot));
123 }
124
125 VLOG(2) << "SyncerThread(" << this << ")" << " Entering start with mode = "
126 << mode;
127
128 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
129 this, &SyncerThread::StartImpl, mode, make_linked_ptr(callback)));
130 }
131
132 void SyncerThread::SendInitialSnapshot() {
133 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
134 scoped_ptr<SyncSession> dummy(new SyncSession(session_context_.get(), this,
135 SyncSourceInfo(), ModelSafeRoutingInfo(),
136 std::vector<ModelSafeWorker*>()));
137 SyncEngineEvent event(SyncEngineEvent::STATUS_CHANGED);
138 sessions::SyncSessionSnapshot snapshot(dummy->TakeSnapshot());
139 event.snapshot = &snapshot;
140 session_context_->NotifyListeners(event);
141 }
142
143 void SyncerThread::WatchConnectionManager() {
144 ServerConnectionManager* scm = session_context_->connection_manager();
145 CheckServerConnectionManagerStatus(scm->server_status());
146 scm->AddListener(this);
147 }
148
149 void SyncerThread::StartImpl(Mode mode,
150 linked_ptr<ModeChangeCallback> callback) {
151 VLOG(2) << "SyncerThread(" << this << ")" << " Doing StartImpl with mode "
152 << mode;
153 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
154 DCHECK(!session_context_->account_name().empty());
155 DCHECK(syncer_.get());
156 mode_ = mode;
157 AdjustPolling(NULL); // Will kick start poll timer if needed.
158 if (callback.get())
159 callback->Run();
160
161 // We just changed our mode. See if there are any pending jobs that we could
162 // execute in the new mode.
163 DoPendingJobIfPossible(false);
164 }
165
166 SyncerThread::JobProcessDecision SyncerThread::DecideWhileInWaitInterval(
167 const SyncSessionJob& job) {
168
169 DCHECK(wait_interval_.get());
170 DCHECK_NE(job.purpose, SyncSessionJob::CLEAR_USER_DATA);
171
172 VLOG(2) << "SyncerThread(" << this << ")" << " Wait interval mode : "
173 << wait_interval_->mode << "Wait interval had nudge : "
174 << wait_interval_->had_nudge << "is canary job : "
175 << job.is_canary_job;
176
177 if (job.purpose == SyncSessionJob::POLL)
178 return DROP;
179
180 DCHECK(job.purpose == SyncSessionJob::NUDGE ||
181 job.purpose == SyncSessionJob::CONFIGURATION);
182 if (wait_interval_->mode == WaitInterval::THROTTLED)
183 return SAVE;
184
185 DCHECK_EQ(wait_interval_->mode, WaitInterval::EXPONENTIAL_BACKOFF);
186 if (job.purpose == SyncSessionJob::NUDGE) {
187 if (mode_ == CONFIGURATION_MODE)
188 return SAVE;
189
190 // If we already had one nudge then just drop this nudge. We will retry
191 // later when the timer runs out.
192 return wait_interval_->had_nudge ? DROP : CONTINUE;
193 }
194 // This is a config job.
195 return job.is_canary_job ? CONTINUE : SAVE;
196 }
197
198 SyncerThread::JobProcessDecision SyncerThread::DecideOnJob(
199 const SyncSessionJob& job) {
200 if (job.purpose == SyncSessionJob::CLEAR_USER_DATA)
201 return CONTINUE;
202
203 if (wait_interval_.get())
204 return DecideWhileInWaitInterval(job);
205
206 if (mode_ == CONFIGURATION_MODE) {
207 if (job.purpose == SyncSessionJob::NUDGE)
208 return SAVE;
209 else if (job.purpose == SyncSessionJob::CONFIGURATION)
210 return CONTINUE;
211 else
212 return DROP;
213 }
214
215 // We are in normal mode.
216 DCHECK_EQ(mode_, NORMAL_MODE);
217 DCHECK_NE(job.purpose, SyncSessionJob::CONFIGURATION);
218
219 // Freshness condition
220 if (job.scheduled_start < last_sync_session_end_time_) {
221 VLOG(2) << "SyncerThread(" << this << ")"
222 << " Dropping job because of freshness";
223 return DROP;
224 }
225
226 if (server_connection_ok_)
227 return CONTINUE;
228
229 VLOG(2) << "SyncerThread(" << this << ")"
230 << " Bad server connection. Using that to decide on job.";
231 return job.purpose == SyncSessionJob::NUDGE ? SAVE : DROP;
232 }
233
234 void SyncerThread::InitOrCoalescePendingJob(const SyncSessionJob& job) {
235 DCHECK(job.purpose != SyncSessionJob::CONFIGURATION);
236 if (pending_nudge_.get() == NULL) {
237 VLOG(2) << "SyncerThread(" << this << ")"
238 << " Creating a pending nudge job";
239 SyncSession* s = job.session.get();
240 scoped_ptr<SyncSession> session(new SyncSession(s->context(),
241 s->delegate(), s->source(), s->routing_info(), s->workers()));
242
243 SyncSessionJob new_job(SyncSessionJob::NUDGE, job.scheduled_start,
244 make_linked_ptr(session.release()), false, job.nudge_location);
245 pending_nudge_.reset(new SyncSessionJob(new_job));
246
74 return; 247 return;
75 } 248 }
76 249
77 NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); 250 VLOG(2) << "SyncerThread(" << this << ")" << " Coalescing a pending nudge";
78 } 251 pending_nudge_->session->Coalesce(*(job.session.get()));
79 252 pending_nudge_->scheduled_start = job.scheduled_start;
80 void SyncerThread::NudgeSyncerWithDataTypes( 253
81 int milliseconds_from_now, 254 // Unfortunately the nudge location cannot be modified. So it stores the
82 NudgeSource source, 255 // location of the first caller.
83 const syncable::ModelTypeBitSet& model_types) { 256 }
84 base::AutoLock lock(lock_); 257
85 if (vault_.syncer_ == NULL) { 258 bool SyncerThread::ShouldRunJob(const SyncSessionJob& job) {
259 JobProcessDecision decision = DecideOnJob(job);
260 VLOG(2) << "SyncerThread(" << this << ")" << " Should run job, decision: "
261 << decision << " Job purpose " << job.purpose << "mode " << mode_;
262 if (decision != SAVE)
263 return decision == CONTINUE;
264
265 DCHECK(job.purpose == SyncSessionJob::NUDGE || job.purpose ==
266 SyncSessionJob::CONFIGURATION);
267
268 SaveJob(job);
269 return false;
270 }
271
272 void SyncerThread::SaveJob(const SyncSessionJob& job) {
273 DCHECK(job.purpose != SyncSessionJob::CLEAR_USER_DATA);
274 if (job.purpose == SyncSessionJob::NUDGE) {
275 VLOG(2) << "SyncerThread(" << this << ")" << " Saving a nudge job";
276 InitOrCoalescePendingJob(job);
277 } else if (job.purpose == SyncSessionJob::CONFIGURATION){
278 VLOG(2) << "SyncerThread(" << this << ")" << " Saving a configuration job";
279 DCHECK(wait_interval_.get());
280 DCHECK(mode_ == CONFIGURATION_MODE);
281
282 SyncSession* old = job.session.get();
283 SyncSession* s(new SyncSession(session_context_.get(), this,
284 old->source(), old->routing_info(), old->workers()));
285 SyncSessionJob new_job(job.purpose, TimeTicks::Now(),
286 make_linked_ptr(s), false, job.nudge_location);
287 wait_interval_->pending_configure_job.reset(new SyncSessionJob(new_job));
288 } // drop the rest.
289 }
290
291 // Functor for std::find_if to search by ModelSafeGroup.
292 struct ModelSafeWorkerGroupIs {
293 explicit ModelSafeWorkerGroupIs(ModelSafeGroup group) : group(group) {}
294 bool operator()(ModelSafeWorker* w) {
295 return group == w->GetModelSafeGroup();
296 }
297 ModelSafeGroup group;
298 };
299
300 void SyncerThread::ScheduleClearUserData() {
301 if (!thread_.IsRunning()) {
302 NOTREACHED();
86 return; 303 return;
87 } 304 }
88 305 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
89 ModelTypePayloadMap model_types_with_payloads = 306 this, &SyncerThread::ScheduleClearUserDataImpl));
90 syncable::ModelTypePayloadMapFromBitSet(model_types, std::string()); 307 }
91 NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); 308
92 } 309 void SyncerThread::ScheduleNudge(const TimeDelta& delay,
93 310 NudgeSource source, const ModelTypeBitSet& types,
94 void SyncerThread::NudgeSyncer( 311 const tracked_objects::Location& nudge_location) {
95 int milliseconds_from_now, 312 if (!thread_.IsRunning()) {
96 NudgeSource source) { 313 NOTREACHED();
97 base::AutoLock lock(lock_);
98 if (vault_.syncer_ == NULL) {
99 return; 314 return;
100 } 315 }
101 316
102 // Set all enabled datatypes. 317 VLOG(2) << "SyncerThread(" << this << ")" << " Nudge scheduled";
318
319 ModelTypePayloadMap types_with_payloads =
320 syncable::ModelTypePayloadMapFromBitSet(types, std::string());
321 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
322 this, &SyncerThread::ScheduleNudgeImpl, delay,
323 GetUpdatesFromNudgeSource(source), types_with_payloads, false,
324 nudge_location));
325 }
326
327 void SyncerThread::ScheduleNudgeWithPayloads(const TimeDelta& delay,
328 NudgeSource source, const ModelTypePayloadMap& types_with_payloads,
329 const tracked_objects::Location& nudge_location) {
330 if (!thread_.IsRunning()) {
331 NOTREACHED();
332 return;
333 }
334
335 VLOG(2) << "SyncerThread(" << this << ")" << " Nudge scheduled with payloads";
336
337 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
338 this, &SyncerThread::ScheduleNudgeImpl, delay,
339 GetUpdatesFromNudgeSource(source), types_with_payloads, false,
340 nudge_location));
341 }
342
343 void SyncerThread::ScheduleClearUserDataImpl() {
344 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
345 SyncSession* session = new SyncSession(session_context_.get(), this,
346 SyncSourceInfo(), ModelSafeRoutingInfo(),
347 std::vector<ModelSafeWorker*>());
348 ScheduleSyncSessionJob(TimeDelta::FromSeconds(0),
349 SyncSessionJob::CLEAR_USER_DATA, session, FROM_HERE);
350 }
351
352 void SyncerThread::ScheduleNudgeImpl(const TimeDelta& delay,
353 GetUpdatesCallerInfo::GetUpdatesSource source,
354 const ModelTypePayloadMap& types_with_payloads,
355 bool is_canary_job, const tracked_objects::Location& nudge_location) {
356 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
357
358 VLOG(2) << "SyncerThread(" << this << ")" << " Running Schedule nudge impl";
359 // Note we currently nudge for all types regardless of the ones incurring
360 // the nudge. Doing different would throw off some syncer commands like
361 // CleanupDisabledTypes. We may want to change this in the future.
362 SyncSourceInfo info(source, types_with_payloads);
363
364 SyncSession* session(CreateSyncSession(info));
365 SyncSessionJob job(SyncSessionJob::NUDGE, TimeTicks::Now() + delay,
366 make_linked_ptr(session), is_canary_job,
367 nudge_location);
368
369 session = NULL;
370 if (!ShouldRunJob(job))
371 return;
372
373 if (pending_nudge_.get()) {
374 if (IsBackingOff() && delay > TimeDelta::FromSeconds(1)) {
375 VLOG(2) << "SyncerThread(" << this << ")" << " Dropping the nudge because"
376 << "we are in backoff";
377 return;
378 }
379
380 VLOG(2) << "SyncerThread(" << this << ")" << " Coalescing pending nudge";
381 pending_nudge_->session->Coalesce(*(job.session.get()));
382
383 if (!IsBackingOff()) {
384 VLOG(2) << "SyncerThread(" << this << ")" << " Dropping a nudge because"
385 << " we are not in backoff and the job was coalesced";
386 return;
387 } else {
388 VLOG(2) << "SyncerThread(" << this << ")"
389 << " Rescheduling pending nudge";
390 SyncSession* s = pending_nudge_->session.get();
391 job.session.reset(new SyncSession(s->context(), s->delegate(),
392 s->source(), s->routing_info(), s->workers()));
393 pending_nudge_.reset();
394 }
395 }
396
397 // TODO(lipalani) - pass the job itself to ScheduleSyncSessionJob.
398 ScheduleSyncSessionJob(delay, SyncSessionJob::NUDGE, job.session.release(),
399 nudge_location);
400 }
401
402 // Helper to extract the routing info and workers corresponding to types in
403 // |types| from |registrar|.
404 void GetModelSafeParamsForTypes(const ModelTypeBitSet& types,
405 ModelSafeWorkerRegistrar* registrar, ModelSafeRoutingInfo* routes,
406 std::vector<ModelSafeWorker*>* workers) {
407 ModelSafeRoutingInfo r_tmp;
408 std::vector<ModelSafeWorker*> w_tmp;
409 registrar->GetModelSafeRoutingInfo(&r_tmp);
410 registrar->GetWorkers(&w_tmp);
411
412 typedef std::vector<ModelSafeWorker*>::const_iterator iter;
413 for (size_t i = syncable::FIRST_REAL_MODEL_TYPE; i < types.size(); ++i) {
414 if (!types.test(i))
415 continue;
416 syncable::ModelType t = syncable::ModelTypeFromInt(i);
417 DCHECK_EQ(1U, r_tmp.count(t));
418 (*routes)[t] = r_tmp[t];
419 iter it = std::find_if(w_tmp.begin(), w_tmp.end(),
420 ModelSafeWorkerGroupIs(r_tmp[t]));
421 if (it != w_tmp.end())
422 workers->push_back(*it);
423 else
424 NOTREACHED();
425 }
426
427 iter it = std::find_if(w_tmp.begin(), w_tmp.end(),
428 ModelSafeWorkerGroupIs(GROUP_PASSIVE));
429 if (it != w_tmp.end())
430 workers->push_back(*it);
431 else
432 NOTREACHED();
433 }
434
435 void SyncerThread::ScheduleConfig(const ModelTypeBitSet& types) {
436 if (!thread_.IsRunning()) {
437 NOTREACHED();
438 return;
439 }
440
441 VLOG(2) << "SyncerThread(" << this << ")" << " Scheduling a config";
103 ModelSafeRoutingInfo routes; 442 ModelSafeRoutingInfo routes;
104 session_context_->registrar()->GetModelSafeRoutingInfo(&routes); 443 std::vector<ModelSafeWorker*> workers;
105 ModelTypePayloadMap model_types_with_payloads = 444 GetModelSafeParamsForTypes(types, session_context_->registrar(),
106 syncable::ModelTypePayloadMapFromRoutingInfo(routes, std::string()); 445 &routes, &workers);
107 NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); 446
108 } 447 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(
109 448 this, &SyncerThread::ScheduleConfigImpl, routes, workers,
110 SyncerThread::SyncerThread(sessions::SyncSessionContext* context) 449 GetUpdatesCallerInfo::FIRST_UPDATE));
111 : thread_main_started_(false, false), 450 }
112 thread_("SyncEngine_SyncerThread"), 451
113 vault_field_changed_(&lock_), 452 void SyncerThread::ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info,
114 conn_mgr_hookup_(NULL), 453 const std::vector<ModelSafeWorker*>& workers,
115 syncer_short_poll_interval_seconds_(kDefaultShortPollIntervalSeconds), 454 const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source) {
116 syncer_long_poll_interval_seconds_(kDefaultLongPollIntervalSeconds), 455 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
117 syncer_polling_interval_(kDefaultShortPollIntervalSeconds), 456
118 syncer_max_interval_(kDefaultMaxPollIntervalMs), 457 VLOG(2) << "SyncerThread(" << this << ")" << " ScheduleConfigImpl...";
119 session_context_(context), 458 // TODO(tim): config-specific GetUpdatesCallerInfo value?
120 disable_idle_detection_(false) { 459 SyncSession* session = new SyncSession(session_context_.get(), this,
121 DCHECK(context); 460 SyncSourceInfo(source,
122 461 syncable::ModelTypePayloadMapFromRoutingInfo(
123 if (context->connection_manager()) 462 routing_info, std::string())),
124 WatchConnectionManager(context->connection_manager()); 463 routing_info, workers);
125 } 464 ScheduleSyncSessionJob(TimeDelta::FromSeconds(0),
126 465 SyncSessionJob::CONFIGURATION, session, FROM_HERE);
127 SyncerThread::~SyncerThread() { 466 }
128 conn_mgr_hookup_.reset(); 467
129 delete vault_.syncer_; 468 void SyncerThread::ScheduleSyncSessionJob(const base::TimeDelta& delay,
130 CHECK(!thread_.IsRunning()); 469 SyncSessionJob::SyncSessionJobPurpose purpose,
131 } 470 sessions::SyncSession* session,
132 471 const tracked_objects::Location& nudge_location) {
133 // Creates and starts a syncer thread. 472 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
134 // Returns true if it creates a thread or if there's currently a thread running 473
135 // and false otherwise. 474 SyncSessionJob job(purpose, TimeTicks::Now() + delay,
136 bool SyncerThread::Start() { 475 make_linked_ptr(session), false, nudge_location);
137 { 476 if (purpose == SyncSessionJob::NUDGE) {
138 base::AutoLock lock(lock_); 477 VLOG(2) << "SyncerThread(" << this << ")" << " Resetting pending_nudge in"
139 if (thread_.IsRunning()) { 478 << " ScheduleSyncSessionJob";
140 return true; 479 DCHECK(!pending_nudge_.get() || pending_nudge_->session.get() == session);
141 } 480 pending_nudge_.reset(new SyncSessionJob(job));
142 481 }
143 if (!thread_.Start()) { 482 VLOG(2) << "SyncerThread(" << this << ")"
144 return false; 483 << " Posting job to execute in DoSyncSessionJob. Job purpose "
145 } 484 << job.purpose;
146 } 485 MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(this,
147 486 &SyncerThread::DoSyncSessionJob, job),
148 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, 487 delay.InMilliseconds());
149 &SyncerThread::ThreadMain)); 488 }
150 489
151 // Wait for notification that our task makes it safely onto the message 490 void SyncerThread::SetSyncerStepsForPurpose(
152 // loop before returning, so the caller can't call Stop before we're 491 SyncSessionJob::SyncSessionJobPurpose purpose,
153 // actually up and running. This is for consistency with the old pthread 492 SyncerStep* start, SyncerStep* end) {
154 // impl because pthread_create would do this in one step. 493 *end = SYNCER_END;
155 thread_main_started_.Wait(); 494 switch (purpose) {
156 VLOG(1) << "SyncerThread started."; 495 case SyncSessionJob::CONFIGURATION:
157 return true; 496 *start = DOWNLOAD_UPDATES;
158 } 497 *end = APPLY_UPDATES;
159
160 // Stop processing. A max wait of at least 2*server RTT time is recommended.
161 // Returns true if we stopped, false otherwise.
162 bool SyncerThread::Stop(int max_wait) {
163 RequestSyncerExitAndSetThreadStopConditions();
164
165 // This will join, and finish when ThreadMain terminates.
166 thread_.Stop();
167 return true;
168 }
169
170 void SyncerThread::RequestSyncerExitAndSetThreadStopConditions() {
171 {
172 base::AutoLock lock(lock_);
173 // If the thread has been started, then we either already have or are about
174 // to enter ThreadMainLoop so we have to proceed with shutdown and wait for
175 // it to finish. If the thread has not been started --and we now own the
176 // lock-- then we can early out because the caller has not called Start().
177 if (!thread_.IsRunning())
178 return; 498 return;
179 499 case SyncSessionJob::CLEAR_USER_DATA:
180 VLOG(1) << "SyncerThread::Stop - setting ThreadMain exit condition to true " 500 *start = CLEAR_PRIVATE_DATA;
181 "(vault_.stop_syncer_thread_)"; 501 return;
182 // Exit the ThreadMainLoop once the syncer finishes (we tell it to exit 502 case SyncSessionJob::NUDGE:
183 // below). 503 case SyncSessionJob::POLL:
184 vault_.stop_syncer_thread_ = true; 504 *start = SYNCER_BEGIN;
185 if (NULL != vault_.syncer_) { 505 return;
186 // Try to early exit the syncer itself, which could be looping inside 506 default:
187 // SyncShare. 507 NOTREACHED();
188 vault_.syncer_->RequestEarlyExit(); 508 }
189 } 509 }
190 510
191 // stop_syncer_thread_ is now true and the Syncer has been told to exit. 511 void SyncerThread::DoSyncSessionJob(const SyncSessionJob& job) {
192 // We want to wake up all waiters so they can re-examine state. We signal, 512 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
193 // causing all waiters to try to re-acquire the lock, and then we release 513 if (!ShouldRunJob(job))
194 // the lock, and join on our internal thread which should soon run off the 514 return;
195 // end of ThreadMain. 515
196 vault_field_changed_.Broadcast(); 516 if (job.purpose == SyncSessionJob::NUDGE) {
197 } 517 DCHECK(pending_nudge_.get());
198 } 518 if (pending_nudge_->session != job.session)
199 519 return; // Another nudge must have been scheduled in in the meantime.
200 bool SyncerThread::RequestPause() { 520 pending_nudge_.reset();
201 base::AutoLock lock(lock_); 521 }
202 if (vault_.pause_requested_ || vault_.paused_) 522 VLOG(2) << "SyncerThread(" << this << ")" << " DoSyncSessionJob. job purpose "
203 return false; 523 << job.purpose;
204 524
205 if (thread_.IsRunning()) { 525 SyncerStep begin(SYNCER_BEGIN);
206 // Set the pause request. The syncer thread will read this 526 SyncerStep end(SYNCER_END);
207 // request, enter the paused state, and send the PAUSED 527 SetSyncerStepsForPurpose(job.purpose, &begin, &end);
208 // notification. 528
209 vault_.pause_requested_ = true; 529 bool has_more_to_sync = true;
210 vault_field_changed_.Broadcast(); 530 while (ShouldRunJob(job) && has_more_to_sync) {
211 VLOG(1) << "Pause requested."; 531 VLOG(2) << "SyncerThread(" << this << ")"
212 } else { 532 << " SyncerThread: Calling SyncShare.";
213 // If the thread is not running, go directly into the paused state 533 // Synchronously perform the sync session from this thread.
214 // and notify. 534 syncer_->SyncShare(job.session.get(), begin, end);
215 EnterPausedState(); 535 has_more_to_sync = job.session->HasMoreToSync();
216 VLOG(1) << "Paused while not running."; 536 if (has_more_to_sync)
217 } 537 job.session->ResetTransientState();
218 return true; 538 }
219 } 539 VLOG(2) << "SyncerThread(" << this << ")"
220 540 << " SyncerThread: Done SyncShare looping.";
221 void SyncerThread::Notify(SyncEngineEvent::EventCause cause) { 541 FinishSyncSessionJob(job);
222 session_context_->NotifyListeners(SyncEngineEvent(cause)); 542 }
223 } 543
224 544 void SyncerThread::UpdateCarryoverSessionState(const SyncSessionJob& old_job) {
225 bool SyncerThread::RequestResume() { 545 if (old_job.purpose == SyncSessionJob::CONFIGURATION) {
226 base::AutoLock lock(lock_); 546 // Whatever types were part of a configuration task will have had updates
227 // Only valid to request a resume when we are already paused or we 547 // downloaded. For that reason, we make sure they get recorded in the
228 // have a pause pending. 548 // event that they get disabled at a later time.
229 if (!(vault_.paused_ || vault_.pause_requested_)) 549 ModelSafeRoutingInfo r(session_context_->previous_session_routing_info());
230 return false; 550 if (!r.empty()) {
231 551 ModelSafeRoutingInfo temp_r;
232 if (thread_.IsRunning()) { 552 ModelSafeRoutingInfo old_info(old_job.session->routing_info());
233 if (vault_.pause_requested_) { 553 std::set_union(r.begin(), r.end(), old_info.begin(), old_info.end(),
234 // If pause was requested we have not yet paused. In this case, 554 std::insert_iterator<ModelSafeRoutingInfo>(temp_r, temp_r.begin()));
235 // the resume cancels the pause request. 555 session_context_->set_previous_session_routing_info(temp_r);
236 vault_.pause_requested_ = false;
237 vault_field_changed_.Broadcast();
238 Notify(SyncEngineEvent::SYNCER_THREAD_RESUMED);
239 VLOG(1) << "Pending pause canceled by resume.";
240 } else {
241 // Unpause and notify.
242 vault_.paused_ = false;
243 vault_field_changed_.Broadcast();
244 } 556 }
245 } else { 557 } else {
246 ExitPausedState(); 558 session_context_->set_previous_session_routing_info(
247 VLOG(1) << "Resumed while not running."; 559 old_job.session->routing_info());
248 } 560 }
249 return true; 561 }
250 } 562
251 563 void SyncerThread::FinishSyncSessionJob(const SyncSessionJob& job) {
252 void SyncerThread::OnReceivedLongPollIntervalUpdate( 564 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
253 const base::TimeDelta& new_interval) { 565 // Update timing information for how often datatypes are triggering nudges.
254 syncer_long_poll_interval_seconds_ = static_cast<int>( 566 base::TimeTicks now = TimeTicks::Now();
255 new_interval.InSeconds()); 567 if (!last_sync_session_end_time_.is_null()) {
256 } 568 ModelTypePayloadMap::const_iterator iter;
257 569 for (iter = job.session->source().types.begin();
258 void SyncerThread::OnReceivedShortPollIntervalUpdate( 570 iter != job.session->source().types.end();
259 const base::TimeDelta& new_interval) { 571 ++iter) {
260 syncer_short_poll_interval_seconds_ = static_cast<int>( 572 syncable::PostTimeToTypeHistogram(iter->first,
261 new_interval.InSeconds()); 573 now - last_sync_session_end_time_);
262 }
263
264 void SyncerThread::OnSilencedUntil(const base::TimeTicks& silenced_until) {
265 silenced_until_ = silenced_until;
266 }
267
268 bool SyncerThread::IsSyncingCurrentlySilenced() {
269 // We should ignore reads from silenced_until_ under ThreadSanitizer
270 // since this is a benign race.
271 ANNOTATE_IGNORE_READS_BEGIN();
272 bool ret = (silenced_until_ - TimeTicks::Now()) >= TimeDelta::FromSeconds(0);
273 ANNOTATE_IGNORE_READS_END();
274 return ret;
275 }
276
277 void SyncerThread::OnShouldStopSyncingPermanently() {
278 RequestSyncerExitAndSetThreadStopConditions();
279 Notify(SyncEngineEvent::STOP_SYNCING_PERMANENTLY);
280 }
281
282 void SyncerThread::ThreadMainLoop() {
283 // This is called with lock_ acquired.
284 lock_.AssertAcquired();
285 VLOG(1) << "In thread main loop.";
286
287 // Use the short poll value by default.
288 vault_.current_wait_interval_.poll_delta =
289 TimeDelta::FromSeconds(syncer_short_poll_interval_seconds_);
290 int user_idle_milliseconds = 0;
291 TimeTicks last_sync_time;
292 bool initial_sync_for_thread = true;
293 bool continue_sync_cycle = false;
294
295 #if defined(OS_LINUX)
296 idle_query_.reset(new IdleQueryLinux());
297 #endif
298
299 if (vault_.syncer_ == NULL) {
300 VLOG(1) << "Syncer thread waiting for database initialization.";
301 while (vault_.syncer_ == NULL && !vault_.stop_syncer_thread_)
302 vault_field_changed_.Wait();
303 VLOG_IF(1, !(vault_.syncer_ == NULL)) << "Syncer was found after DB "
304 "started.";
305 }
306
307 while (!vault_.stop_syncer_thread_) {
308 // The Wait()s in these conditionals using |vault_| are not TimedWait()s (as
309 // below) because we cannot poll until these conditions are met, so we wait
310 // indefinitely.
311
312 // If we are not connected, enter WaitUntilConnectedOrQuit() which
313 // will return only when the network is connected or a quit is
314 // requested. Note that it is possible to exit
315 // WaitUntilConnectedOrQuit() in the paused state which will be
316 // handled by the next statement.
317 if (!vault_.connected_ && !initial_sync_for_thread) {
318 WaitUntilConnectedOrQuit();
319 continue;
320 } 574 }
321 575 }
322 // Check if we should be paused or if a pause was requested. Note 576 last_sync_session_end_time_ = now;
323 // that we don't check initial_sync_for_thread here since we want 577 UpdateCarryoverSessionState(job);
324 // the pause to happen regardless if it is the initial sync or not. 578 if (IsSyncingCurrentlySilenced()) {
325 if (vault_.pause_requested_ || vault_.paused_) { 579 VLOG(2) << "SyncerThread(" << this << ")"
326 PauseUntilResumedOrQuit(); 580 << " We are currently throttled. So not scheduling the next sync.";
327 continue; 581 SaveJob(job);
582 return; // Nothing to do.
583 }
584
585 VLOG(2) << "SyncerThread(" << this << ")"
586 << " Updating the next polling time after SyncMain";
587 ScheduleNextSync(job);
588 }
589
590 void SyncerThread::ScheduleNextSync(const SyncSessionJob& old_job) {
591 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
592 DCHECK(!old_job.session->HasMoreToSync());
593 // Note: |num_server_changes_remaining| > 0 here implies that we received a
594 // broken response while trying to download all updates, because the Syncer
595 // will loop until this value is exhausted. Also, if unsynced_handles exist
596 // but HasMoreToSync is false, this implies that the Syncer determined no
597 // forward progress was possible at this time (an error, such as an HTTP
598 // 500, is likely to have occurred during commit).
599 const bool work_to_do =
600 old_job.session->status_controller()->num_server_changes_remaining() > 0
601 || old_job.session->status_controller()->unsynced_handles().size() > 0;
602 VLOG(2) << "SyncerThread(" << this << ")" << " syncer has work to do: "
603 << work_to_do;
604
605 AdjustPolling(&old_job);
606
607 // TODO(tim): Old impl had special code if notifications disabled. Needed?
608 if (!work_to_do) {
609 // Success implies backoff relief. Note that if this was a "one-off" job
610 // (i.e. purpose == SyncSessionJob::CLEAR_USER_DATA), if there was
611 // work_to_do before it ran this wont have changed, as jobs like this don't
612 // run a full sync cycle. So we don't need special code here.
613 wait_interval_.reset();
614 VLOG(2) << "SyncerThread(" << this << ")"
615 << " Job suceeded so not scheduling more jobs";
616 return;
617 }
618
619 if (old_job.session->source().updates_source ==
620 GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION) {
621 VLOG(2) << "SyncerThread(" << this << ")"
622 << " Job failed with source continuation";
623 // We don't seem to have made forward progress. Start or extend backoff.
624 HandleConsecutiveContinuationError(old_job);
625 } else if (IsBackingOff()) {
626 VLOG(2) << "SyncerThread(" << this << ")"
627 << " A nudge during backoff failed";
628 // We weren't continuing but we're in backoff; must have been a nudge.
629 DCHECK_EQ(SyncSessionJob::NUDGE, old_job.purpose);
630 DCHECK(!wait_interval_->had_nudge);
631 wait_interval_->had_nudge = true;
632 wait_interval_->timer.Reset();
633 } else {
634 VLOG(2) << "SyncerThread(" << this << ")"
635 << " Failed. Schedule a job with continuation as source";
636 // We weren't continuing and we aren't in backoff. Schedule a normal
637 // continuation.
638 if (old_job.purpose == SyncSessionJob::CONFIGURATION) {
639 ScheduleConfigImpl(old_job.session->routing_info(),
640 old_job.session->workers(),
641 GetUpdatesFromNudgeSource(NUDGE_SOURCE_CONTINUATION));
642 } else {
643 // For all other purposes(nudge and poll) we schedule a retry nudge.
644 ScheduleNudgeImpl(TimeDelta::FromSeconds(0),
645 GetUpdatesFromNudgeSource(NUDGE_SOURCE_CONTINUATION),
646 old_job.session->source().types, false, FROM_HERE);
328 } 647 }
329 648 }
330 const TimeTicks next_poll = last_sync_time + 649 }
331 vault_.current_wait_interval_.poll_delta; 650
332 bool throttled = vault_.current_wait_interval_.mode == 651 void SyncerThread::AdjustPolling(const SyncSessionJob* old_job) {
333 WaitInterval::THROTTLED; 652 DCHECK(thread_.IsRunning());
334 // If we are throttled, we must wait. Otherwise, wait until either the next 653 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
335 // nudge (if one exists) or the poll interval. 654
336 TimeTicks end_wait = next_poll; 655 TimeDelta poll = (!session_context_->notifications_enabled()) ?
337 if (!throttled && !vault_.pending_nudge_time_.is_null()) {
338 end_wait = std::min(end_wait, vault_.pending_nudge_time_);
339 }
340 VLOG(1) << "end_wait is " << end_wait.ToInternalValue()
341 << "\nnext_poll is " << next_poll.ToInternalValue();
342
343 // We block until the CV is signaled (e.g a control field changed, loss of
344 // network connection, nudge, spurious, etc), or the poll interval elapses.
345 TimeDelta sleep_time = end_wait - TimeTicks::Now();
346 if (!initial_sync_for_thread && sleep_time > TimeDelta::FromSeconds(0)) {
347 vault_field_changed_.TimedWait(sleep_time);
348
349 if (TimeTicks::Now() < end_wait) {
350 // Didn't timeout. Could be a spurious signal, or a signal corresponding
351 // to an actual change in one of our control fields. By continuing here
352 // we perform the typical "always recheck conditions when signaled",
353 // (typically handled by a while(condition_not_met) cv.wait() construct)
354 // because we jump to the top of the loop. The main difference is we
355 // recalculate the wait interval, but last_sync_time won't have changed.
356 // So if we were signaled by a nudge (for ex.) we'll grab the new nudge
357 // off the queue and wait for that delta. If it was a spurious signal,
358 // we'll keep waiting for the same moment in time as we just were.
359 continue;
360 }
361 }
362
363 // Handle a nudge, caused by either a notification or a local bookmark
364 // event. This will also update the source of the following SyncMain call.
365 VLOG(1) << "Calling Sync Main at time " << Time::Now().ToInternalValue();
366 bool nudged = false;
367 scoped_ptr<SyncSession> session;
368 session.reset(SyncMain(vault_.syncer_,
369 throttled, continue_sync_cycle, &initial_sync_for_thread, &nudged));
370
371 // Update timing information for how often these datatypes are triggering
372 // nudges.
373 base::TimeTicks now = TimeTicks::Now();
374 if (!last_sync_time.is_null()) {
375 ModelTypePayloadMap::const_iterator iter;
376 for (iter = session->source().types.begin();
377 iter != session->source().types.end();
378 ++iter) {
379 syncable::PostTimeToTypeHistogram(iter->first,
380 now - last_sync_time);
381 }
382 }
383
384 last_sync_time = now;
385
386 VLOG(1) << "Updating the next polling time after SyncMain";
387 vault_.current_wait_interval_ = CalculatePollingWaitTime(
388 static_cast<int>(vault_.current_wait_interval_.poll_delta.InSeconds()),
389 &user_idle_milliseconds, &continue_sync_cycle, nudged);
390 }
391 #if defined(OS_LINUX)
392 idle_query_.reset();
393 #endif
394 }
395
396 void SyncerThread::SetConnected(bool connected) {
397 DCHECK(!thread_.IsRunning());
398 vault_.connected_ = connected;
399 }
400
401 void SyncerThread::SetSyncerPollingInterval(base::TimeDelta interval) {
402 // TODO(timsteele): Use TimeDelta internally.
403 syncer_polling_interval_ = static_cast<int>(interval.InSeconds());
404 }
405
406 void SyncerThread::SetSyncerShortPollInterval(base::TimeDelta interval) {
407 // TODO(timsteele): Use TimeDelta internally.
408 syncer_short_poll_interval_seconds_ =
409 static_cast<int>(interval.InSeconds());
410 }
411
412 void SyncerThread::WaitUntilConnectedOrQuit() {
413 VLOG(1) << "Syncer thread waiting for connection.";
414 Notify(SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION);
415
416 bool is_paused = vault_.paused_;
417
418 while (!vault_.connected_ && !vault_.stop_syncer_thread_) {
419 if (!is_paused && vault_.pause_requested_) {
420 // If we get a pause request while waiting for a connection,
421 // enter the paused state.
422 EnterPausedState();
423 is_paused = true;
424 VLOG(1) << "Syncer thread entering disconnected pause.";
425 }
426
427 if (is_paused && !vault_.paused_) {
428 ExitPausedState();
429 is_paused = false;
430 VLOG(1) << "Syncer thread exiting disconnected pause.";
431 }
432
433 vault_field_changed_.Wait();
434 }
435
436 if (!vault_.stop_syncer_thread_) {
437 Notify(SyncEngineEvent::SYNCER_THREAD_CONNECTED);
438 VLOG(1) << "Syncer thread found connection.";
439 }
440 }
441
442 void SyncerThread::PauseUntilResumedOrQuit() {
443 VLOG(1) << "Syncer thread entering pause.";
444 // If pause was requested (rather than already being paused), send
445 // the PAUSED notification.
446 if (vault_.pause_requested_)
447 EnterPausedState();
448
449 // Thread will get stuck here until either a resume is requested
450 // or shutdown is started.
451 while (vault_.paused_ && !vault_.stop_syncer_thread_)
452 vault_field_changed_.Wait();
453
454 // Notify that we have resumed if we are not shutting down.
455 if (!vault_.stop_syncer_thread_)
456 ExitPausedState();
457
458 VLOG(1) << "Syncer thread exiting pause.";
459 }
460
461 void SyncerThread::EnterPausedState() {
462 lock_.AssertAcquired();
463 vault_.pause_requested_ = false;
464 vault_.paused_ = true;
465 vault_field_changed_.Broadcast();
466 Notify(SyncEngineEvent::SYNCER_THREAD_PAUSED);
467 }
468
469 void SyncerThread::ExitPausedState() {
470 lock_.AssertAcquired();
471 vault_.paused_ = false;
472 vault_field_changed_.Broadcast();
473 Notify(SyncEngineEvent::SYNCER_THREAD_RESUMED);
474 }
475
476 void SyncerThread::DisableIdleDetection() {
477 disable_idle_detection_ = true;
478 }
479
480 // We check how long the user's been idle and sync less often if the machine is
481 // not in use. The aim is to reduce server load.
482 SyncerThread::WaitInterval SyncerThread::CalculatePollingWaitTime(
483 int last_poll_wait, // Time in seconds.
484 int* user_idle_milliseconds,
485 bool* continue_sync_cycle,
486 bool was_nudged) {
487 lock_.AssertAcquired(); // We access 'vault' in here, so we need the lock.
488 WaitInterval return_interval;
489
490 // Server initiated throttling trumps everything.
491 if (!silenced_until_.is_null()) {
492 // We don't need to reset other state, it can continue where it left off.
493 return_interval.mode = WaitInterval::THROTTLED;
494 return_interval.poll_delta = silenced_until_ - TimeTicks::Now();
495 return return_interval;
496 }
497
498 bool is_continuing_sync_cyle = *continue_sync_cycle;
499 *continue_sync_cycle = false;
500
501 // Determine if the syncer has unfinished work to do.
502 SyncSessionSnapshot* snapshot = session_context_->previous_session_snapshot();
503 const bool syncer_has_work_to_do = snapshot &&
504 (snapshot->num_server_changes_remaining > 0 ||
505 snapshot->unsynced_count > 0);
506 VLOG(1) << "syncer_has_work_to_do is " << syncer_has_work_to_do;
507
508 // First calculate the expected wait time, figuring in any backoff because of
509 // user idle time. next_wait is in seconds
510 syncer_polling_interval_ = (!session_context_->notifications_enabled()) ?
511 syncer_short_poll_interval_seconds_ : 656 syncer_short_poll_interval_seconds_ :
512 syncer_long_poll_interval_seconds_; 657 syncer_long_poll_interval_seconds_;
513 int default_next_wait = syncer_polling_interval_; 658 bool rate_changed = !poll_timer_.IsRunning() ||
514 return_interval.poll_delta = TimeDelta::FromSeconds(default_next_wait); 659 poll != poll_timer_.GetCurrentDelay();
515 660
516 if (syncer_has_work_to_do) { 661 if (old_job && old_job->purpose != SyncSessionJob::POLL && !rate_changed)
517 // Provide exponential backoff due to consecutive errors, else attempt to 662 poll_timer_.Reset();
518 // complete the work as soon as possible. 663
519 if (is_continuing_sync_cyle) { 664 if (!rate_changed)
520 return_interval.mode = WaitInterval::EXPONENTIAL_BACKOFF; 665 return;
521 if (was_nudged && vault_.current_wait_interval_.mode == 666
522 WaitInterval::EXPONENTIAL_BACKOFF) { 667 // Adjust poll rate.
523 // We were nudged, it failed, and we were already in backoff. 668 poll_timer_.Stop();
524 return_interval.had_nudge_during_backoff = true; 669 poll_timer_.Start(poll, this, &SyncerThread::PollTimerCallback);
525 // Keep exponent for exponential backoff the same in this case. 670 }
526 return_interval.poll_delta = vault_.current_wait_interval_.poll_delta; 671
527 } else { 672 void SyncerThread::HandleConsecutiveContinuationError(
528 // We weren't nudged, or we were in a NORMAL wait interval until now. 673 const SyncSessionJob& old_job) {
529 return_interval.poll_delta = TimeDelta::FromSeconds( 674 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
530 GetRecommendedDelaySeconds(last_poll_wait)); 675 // This if conditions should be compiled out in retail builds.
531 } 676 if (IsBackingOff()) {
532 } else { 677 DCHECK(wait_interval_->timer.IsRunning() || old_job.is_canary_job);
533 // No consecutive error. 678 }
534 return_interval.poll_delta = TimeDelta::FromSeconds( 679 SyncSession* old = old_job.session.get();
535 GetRecommendedDelaySeconds(0)); 680 SyncSession* s(new SyncSession(session_context_.get(), this,
536 } 681 old->source(), old->routing_info(), old->workers()));
537 *continue_sync_cycle = true; 682 TimeDelta length = delay_provider_->GetDelay(
538 } else if (!session_context_->notifications_enabled()) { 683 IsBackingOff() ? wait_interval_->length : TimeDelta::FromSeconds(1));
539 // Ensure that we start exponential backoff from our base polling 684
540 // interval when we are not continuing a sync cycle. 685 VLOG(2) << "SyncerThread(" << this << ")"
541 last_poll_wait = std::max(last_poll_wait, syncer_polling_interval_); 686 << " In handle continuation error. Old job purpose is "
542 687 << old_job.purpose;
543 // Did the user start interacting with the computer again? 688 VLOG(2) << "SyncerThread(" << this << ")"
544 // If so, revise our idle time (and probably next_sync_time) downwards 689 << " In Handle continuation error. The time delta(ms) is: "
545 int new_idle_time = disable_idle_detection_ ? 0 : UserIdleTime(); 690 << length.InMilliseconds();
546 if (new_idle_time < *user_idle_milliseconds) { 691
547 *user_idle_milliseconds = new_idle_time; 692 // This will reset the had_nudge variable as well.
548 } 693 wait_interval_.reset(new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF,
549 return_interval.poll_delta = TimeDelta::FromMilliseconds( 694 length));
550 CalculateSyncWaitTime(last_poll_wait * 1000, 695 if (old_job.purpose == SyncSessionJob::CONFIGURATION) {
551 *user_idle_milliseconds)); 696 SyncSessionJob job(old_job.purpose, TimeTicks::Now() + length,
552 DCHECK_GE(return_interval.poll_delta.InSeconds(), default_next_wait); 697 make_linked_ptr(s), false, FROM_HERE);
553 } 698 wait_interval_->pending_configure_job.reset(new SyncSessionJob(job));
554 699 } else {
555 VLOG(1) << "Sync wait: idle " << default_next_wait 700 // We are not in configuration mode. So wait_interval's pending job
556 << " non-idle or backoff " << return_interval.poll_delta.InSeconds(); 701 // should be null.
557 702 DCHECK(wait_interval_->pending_configure_job.get() == NULL);
558 return return_interval; 703
559 } 704 // TODO(lipalani) - handle clear user data.
560 705 InitOrCoalescePendingJob(old_job);
561 void SyncerThread::ThreadMain() { 706 }
562 base::AutoLock lock(lock_); 707 wait_interval_->timer.Start(length, this, &SyncerThread::DoCanaryJob);
563 // Signal Start() to let it know we've made it safely onto the message loop, 708 }
564 // and unblock it's caller. 709
565 thread_main_started_.Signal(); 710 // static
566 ThreadMainLoop(); 711 TimeDelta SyncerThread::GetRecommendedDelay(const TimeDelta& last_delay) {
567 VLOG(1) << "Syncer thread ThreadMain is done."; 712 if (last_delay.InSeconds() >= kMaxBackoffSeconds)
568 Notify(SyncEngineEvent::SYNCER_THREAD_EXITING); 713 return TimeDelta::FromSeconds(kMaxBackoffSeconds);
569 } 714
570 715 // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2
571 SyncSession* SyncerThread::SyncMain(Syncer* syncer, bool was_throttled, 716 int64 backoff_s =
572 bool continue_sync_cycle, bool* initial_sync_for_thread, 717 std::max(static_cast<int64>(1),
573 bool* was_nudged) { 718 last_delay.InSeconds() * kBackoffRandomizationFactor);
574 CHECK(syncer); 719
575 720 // Flip a coin to randomize backoff interval by +/- 50%.
576 // Since we are initiating a new session for which we are the delegate, we 721 int rand_sign = base::RandInt(0, 1) * 2 - 1;
577 // are not currently silenced so reset this state for the next session which 722
578 // may need to use it. 723 // Truncation is adequate for rounding here.
579 silenced_until_ = base::TimeTicks(); 724 backoff_s = backoff_s +
580 725 (rand_sign * (last_delay.InSeconds() / kBackoffRandomizationFactor));
726
727 // Cap the backoff interval.
728 backoff_s = std::max(static_cast<int64>(1),
729 std::min(backoff_s, kMaxBackoffSeconds));
730
731 return TimeDelta::FromSeconds(backoff_s);
732 }
733
734 void SyncerThread::Stop() {
735 VLOG(2) << "SyncerThread(" << this << ")" << " stop called";
736 syncer_->RequestEarlyExit(); // Safe to call from any thread.
737 session_context_->connection_manager()->RemoveListener(this);
738 thread_.Stop();
739 }
740
741 void SyncerThread::DoCanaryJob() {
742 VLOG(2) << "SyncerThread(" << this << ")" << " Do canary job";
743 DoPendingJobIfPossible(true);
744 }
745
746 void SyncerThread::DoPendingJobIfPossible(bool is_canary_job) {
747 SyncSessionJob* job_to_execute = NULL;
748 if (mode_ == CONFIGURATION_MODE && wait_interval_.get()
749 && wait_interval_->pending_configure_job.get()) {
750 VLOG(2) << "SyncerThread(" << this << ")" << " Found pending configure job";
751 job_to_execute = wait_interval_->pending_configure_job.get();
752 } else if (mode_ == NORMAL_MODE && pending_nudge_.get()) {
753 VLOG(2) << "SyncerThread(" << this << ")" << " Found pending nudge job";
754 // Pending jobs mostly have time from the past. Reset it so this job
755 // will get executed.
756 if (pending_nudge_->scheduled_start < TimeTicks::Now())
757 pending_nudge_->scheduled_start = TimeTicks::Now();
758
759 scoped_ptr<SyncSession> session(CreateSyncSession(
760 pending_nudge_->session->source()));
761
762 // Also the routing info might have been changed since we cached the
763 // pending nudge. Update it by coalescing to the latest.
764 pending_nudge_->session->Coalesce(*(session.get()));
765 // The pending nudge would be cleared in the DoSyncSessionJob function.
766 job_to_execute = pending_nudge_.get();
767 }
768
769 if (job_to_execute != NULL) {
770 VLOG(2) << "SyncerThread(" << this << ")" << " Executing pending job";
771 SyncSessionJob copy = *job_to_execute;
772 copy.is_canary_job = is_canary_job;
773 DoSyncSessionJob(copy);
774 }
775 }
776
777 SyncSession* SyncerThread::CreateSyncSession(const SyncSourceInfo& source) {
581 ModelSafeRoutingInfo routes; 778 ModelSafeRoutingInfo routes;
582 std::vector<ModelSafeWorker*> workers; 779 std::vector<ModelSafeWorker*> workers;
583 session_context_->registrar()->GetModelSafeRoutingInfo(&routes); 780 session_context_->registrar()->GetModelSafeRoutingInfo(&routes);
584 session_context_->registrar()->GetWorkers(&workers); 781 session_context_->registrar()->GetWorkers(&workers);
585 SyncSourceInfo info(GetAndResetNudgeSource(was_throttled, 782 SyncSourceInfo info(source);
586 continue_sync_cycle, initial_sync_for_thread, was_nudged)); 783
587 scoped_ptr<SyncSession> session; 784 SyncSession* session(new SyncSession(session_context_.get(), this, info,
588 785 routes, workers));
589 base::AutoUnlock unlock(lock_); 786
590 do { 787 return session;
591 session.reset(new SyncSession(session_context_.get(), this, 788 }
592 info, routes, workers)); 789
593 VLOG(1) << "Calling SyncShare."; 790 void SyncerThread::PollTimerCallback() {
594 syncer->SyncShare(session.get()); 791 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
595 } while (session->HasMoreToSync() && silenced_until_.is_null()); 792 ModelSafeRoutingInfo r;
596 793 ModelTypePayloadMap types_with_payloads =
597 VLOG(1) << "Done calling SyncShare."; 794 syncable::ModelTypePayloadMapFromRoutingInfo(r, std::string());
598 return session.release(); 795 SyncSourceInfo info(GetUpdatesCallerInfo::PERIODIC, types_with_payloads);
599 } 796 SyncSession* s = CreateSyncSession(info);
600 797 ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), SyncSessionJob::POLL, s,
601 SyncSourceInfo SyncerThread::GetAndResetNudgeSource(bool was_throttled, 798 FROM_HERE);
602 bool continue_sync_cycle, 799 }
603 bool* initial_sync, 800
604 bool* was_nudged) { 801 void SyncerThread::Unthrottle() {
605 bool nudged = false; 802 DCHECK_EQ(WaitInterval::THROTTLED, wait_interval_->mode);
606 NudgeSource nudge_source = kUnknown; 803 VLOG(2) << "SyncerThread(" << this << ")" << " Unthrottled..";
607 ModelTypePayloadMap model_types_with_payloads; 804 DoCanaryJob();
608 // Has the previous sync cycle completed? 805 wait_interval_.reset();
609 if (continue_sync_cycle) 806 }
610 nudge_source = kContinuation; 807
611 // Update the nudge source if a new nudge has come through during the 808 void SyncerThread::Notify(SyncEngineEvent::EventCause cause) {
612 // previous sync cycle. 809 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
613 if (!vault_.pending_nudge_time_.is_null()) { 810 session_context_->NotifyListeners(SyncEngineEvent(cause));
614 if (!was_throttled) { 811 }
615 nudge_source = vault_.pending_nudge_source_; 812
616 model_types_with_payloads = vault_.pending_nudge_types_; 813 bool SyncerThread::IsBackingOff() const {
617 nudged = true; 814 return wait_interval_.get() && wait_interval_->mode ==
618 } 815 WaitInterval::EXPONENTIAL_BACKOFF;
619 VLOG(1) << "Clearing pending nudge from " << vault_.pending_nudge_source_ 816 }
620 << " at tick " << vault_.pending_nudge_time_.ToInternalValue(); 817
621 vault_.pending_nudge_source_ = kUnknown; 818 void SyncerThread::OnSilencedUntil(const base::TimeTicks& silenced_until) {
622 vault_.pending_nudge_types_.clear(); 819 wait_interval_.reset(new WaitInterval(WaitInterval::THROTTLED,
623 vault_.pending_nudge_time_ = base::TimeTicks(); 820 silenced_until - TimeTicks::Now()));
624 } 821 wait_interval_->timer.Start(wait_interval_->length, this,
625 822 &SyncerThread::Unthrottle);
626 *was_nudged = nudged; 823 }
627 824
628 // TODO(tim): Hack for bug 64136 to correctly tag continuations that result 825 bool SyncerThread::IsSyncingCurrentlySilenced() {
629 // from syncer having more work to do. This will be handled properly with 826 return wait_interval_.get() && wait_interval_->mode ==
630 // the message loop based syncer thread, bug 26339. 827 WaitInterval::THROTTLED;
631 return MakeSyncSourceInfo(nudged || nudge_source == kContinuation, 828 }
632 nudge_source, model_types_with_payloads, initial_sync); 829
633 } 830 void SyncerThread::OnReceivedShortPollIntervalUpdate(
634 831 const base::TimeDelta& new_interval) {
635 SyncSourceInfo SyncerThread::MakeSyncSourceInfo(bool nudged, 832 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
636 NudgeSource nudge_source, 833 syncer_short_poll_interval_seconds_ = new_interval;
637 const ModelTypePayloadMap& model_types_with_payloads, 834 }
638 bool* initial_sync) { 835
639 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource updates_source = 836 void SyncerThread::OnReceivedLongPollIntervalUpdate(
640 sync_pb::GetUpdatesCallerInfo::UNKNOWN; 837 const base::TimeDelta& new_interval) {
641 if (*initial_sync) { 838 DCHECK_EQ(MessageLoop::current(), thread_.message_loop());
642 updates_source = sync_pb::GetUpdatesCallerInfo::FIRST_UPDATE; 839 syncer_long_poll_interval_seconds_ = new_interval;
643 *initial_sync = false; 840 }
644 } else if (!nudged) { 841
645 updates_source = sync_pb::GetUpdatesCallerInfo::PERIODIC; 842 void SyncerThread::OnShouldStopSyncingPermanently() {
646 } else { 843 VLOG(2) << "SyncerThread(" << this << ")"
647 switch (nudge_source) { 844 << " OnShouldStopSyncingPermanently";
648 case kNotification: 845 syncer_->RequestEarlyExit(); // Thread-safe.
649 updates_source = sync_pb::GetUpdatesCallerInfo::NOTIFICATION; 846 Notify(SyncEngineEvent::STOP_SYNCING_PERMANENTLY);
650 break; 847 }
651 case kLocal: 848
652 updates_source = sync_pb::GetUpdatesCallerInfo::LOCAL; 849 void SyncerThread::OnServerConnectionEvent(
653 break; 850 const ServerConnectionEvent2& event) {
654 case kContinuation: 851 thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this,
655 updates_source = sync_pb::GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION; 852 &SyncerThread::CheckServerConnectionManagerStatus,
656 break; 853 event.connection_code));
657 case kClearPrivateData: 854 }
658 updates_source = sync_pb::GetUpdatesCallerInfo::CLEAR_PRIVATE_DATA; 855
659 break; 856 void SyncerThread::set_notifications_enabled(bool notifications_enabled) {
660 case kUnknown:
661 default:
662 updates_source = sync_pb::GetUpdatesCallerInfo::UNKNOWN;
663 break;
664 }
665 }
666
667 ModelTypePayloadMap sync_source_types;
668 if (model_types_with_payloads.empty()) {
669 // No datatypes requested. This must be a poll so set all enabled datatypes.
670 ModelSafeRoutingInfo routes;
671 session_context_->registrar()->GetModelSafeRoutingInfo(&routes);
672 sync_source_types = syncable::ModelTypePayloadMapFromRoutingInfo(routes,
673 std::string());
674 } else {
675 sync_source_types = model_types_with_payloads;
676 }
677
678 return SyncSourceInfo(updates_source, sync_source_types);
679 }
680
681 void SyncerThread::CreateSyncer(const std::string& dirname) {
682 base::AutoLock lock(lock_);
683 VLOG(1) << "Creating syncer up for: " << dirname;
684 // The underlying database structure is ready, and we should create
685 // the syncer.
686 CHECK(vault_.syncer_ == NULL);
687 vault_.syncer_ = new Syncer();
688 vault_field_changed_.Broadcast();
689 }
690
691 // Sets |*connected| to false if it is currently true but |code| suggests that
692 // the current network configuration and/or auth state cannot be used to make
693 // forward progress, and user intervention (e.g changing server URL or auth
694 // credentials) is likely necessary. If |*connected| is false, set it to true
695 // if |code| suggests that we just recently made healthy contact with the
696 // server.
697 static inline void CheckConnected(bool* connected,
698 HttpResponse::ServerConnectionCode code,
699 base::ConditionVariable* condvar) {
700 if (*connected) {
701 // Note, be careful when adding cases here because if the SyncerThread
702 // thinks there is no valid connection as determined by this method, it
703 // will drop out of *all* forward progress sync loops (it won't poll and it
704 // will queue up Talk notifications but not actually call SyncShare) until
705 // some external action causes a ServerConnectionManager to broadcast that
706 // a valid connection has been re-established.
707 if (HttpResponse::CONNECTION_UNAVAILABLE == code ||
708 HttpResponse::SYNC_AUTH_ERROR == code) {
709 *connected = false;
710 condvar->Broadcast();
711 }
712 } else {
713 if (HttpResponse::SERVER_CONNECTION_OK == code) {
714 *connected = true;
715 condvar->Broadcast();
716 }
717 }
718 }
719
720 void SyncerThread::WatchConnectionManager(ServerConnectionManager* conn_mgr) {
721 conn_mgr_hookup_.reset(NewEventListenerHookup(conn_mgr->channel(), this,
722 &SyncerThread::HandleServerConnectionEvent));
723 CheckConnected(&vault_.connected_, conn_mgr->server_status(),
724 &vault_field_changed_);
725 }
726
727 void SyncerThread::HandleServerConnectionEvent(
728 const ServerConnectionEvent& event) {
729 if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) {
730 base::AutoLock lock(lock_);
731 CheckConnected(&vault_.connected_, event.connection_code,
732 &vault_field_changed_);
733 }
734 }
735
736 int SyncerThread::GetRecommendedDelaySeconds(int base_delay_seconds) {
737 if (base_delay_seconds >= kMaxBackoffSeconds)
738 return kMaxBackoffSeconds;
739
740 // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2
741 int backoff_s =
742 std::max(1, base_delay_seconds * kBackoffRandomizationFactor);
743
744 // Flip a coin to randomize backoff interval by +/- 50%.
745 int rand_sign = base::RandInt(0, 1) * 2 - 1;
746
747 // Truncation is adequate for rounding here.
748 backoff_s = backoff_s +
749 (rand_sign * (base_delay_seconds / kBackoffRandomizationFactor));
750
751 // Cap the backoff interval.
752 backoff_s = std::max(1, std::min(backoff_s, kMaxBackoffSeconds));
753
754 return backoff_s;
755 }
756
757 // Inputs and return value in milliseconds.
758 int SyncerThread::CalculateSyncWaitTime(int last_interval, int user_idle_ms) {
759 // syncer_polling_interval_ is in seconds
760 int syncer_polling_interval_ms = syncer_polling_interval_ * 1000;
761
762 // This is our default and lower bound.
763 int next_wait = syncer_polling_interval_ms;
764
765 // Get idle time, bounded by max wait.
766 int idle = min(user_idle_ms, syncer_max_interval_);
767
768 // If the user has been idle for a while, we'll start decreasing the poll
769 // rate.
770 if (idle >= kPollBackoffThresholdMultiplier * syncer_polling_interval_ms) {
771 next_wait = std::min(GetRecommendedDelaySeconds(
772 last_interval / 1000), syncer_max_interval_ / 1000) * 1000;
773 }
774
775 return next_wait;
776 }
777
778 // Called with mutex_ already locked.
779 void SyncerThread::NudgeSyncImpl(
780 int milliseconds_from_now,
781 NudgeSource source,
782 const ModelTypePayloadMap& model_types_with_payloads) {
783 // TODO(sync): Add the option to reset the backoff state machine.
784 // This is needed so nudges that are a result of the user's desire
785 // to download updates for a new data type can be satisfied quickly.
786 if (vault_.current_wait_interval_.mode == WaitInterval::THROTTLED ||
787 vault_.current_wait_interval_.had_nudge_during_backoff) {
788 // Drop nudges on the floor if we've already had one since starting this
789 // stage of exponential backoff or we are throttled.
790 return;
791 }
792
793 // Union the current ModelTypePayloadMap with any from nudges that may have
794 // already posted (coalesce the nudge datatype information).
795 // TODO(tim): It seems weird to do this if the sources don't match up (e.g.
796 // if pending_source is kLocal and |source| is kClearPrivateData).
797 syncable::CoalescePayloads(&vault_.pending_nudge_types_,
798 model_types_with_payloads);
799
800 const TimeTicks nudge_time = TimeTicks::Now() +
801 TimeDelta::FromMilliseconds(milliseconds_from_now);
802 if (nudge_time <= vault_.pending_nudge_time_) {
803 VLOG(1) << "Nudge for source " << source
804 << " dropped due to existing later pending nudge";
805 return;
806 }
807
808 VLOG(1) << "Replacing pending nudge for source " << source
809 << " at " << nudge_time.ToInternalValue();
810
811 vault_.pending_nudge_source_ = source;
812 vault_.pending_nudge_time_ = nudge_time;
813 vault_field_changed_.Broadcast();
814 }
815
816 void SyncerThread::SetNotificationsEnabled(bool notifications_enabled) {
817 base::AutoLock lock(lock_);
818 session_context_->set_notifications_enabled(notifications_enabled); 857 session_context_->set_notifications_enabled(notifications_enabled);
819 } 858 }
820 859
821 // Returns the amount of time since the user last interacted with the computer, 860 } // browser_sync
822 // in milliseconds
823 int SyncerThread::UserIdleTime() {
824 #if defined(OS_WIN)
825 LASTINPUTINFO last_input_info;
826 last_input_info.cbSize = sizeof(LASTINPUTINFO);
827
828 // Get time in windows ticks since system start of last activity.
829 BOOL b = ::GetLastInputInfo(&last_input_info);
830 if (b == TRUE)
831 return ::GetTickCount() - last_input_info.dwTime;
832 #elif defined(OS_MACOSX)
833 // It would be great to do something like:
834 //
835 // return 1000 *
836 // CGEventSourceSecondsSinceLastEventType(
837 // kCGEventSourceStateCombinedSessionState,
838 // kCGAnyInputEventType);
839 //
840 // Unfortunately, CGEvent* lives in ApplicationServices, and we're a daemon
841 // and can't link that high up the food chain. Thus this mucking in IOKit.
842
843 io_service_t hid_service =
844 IOServiceGetMatchingService(kIOMasterPortDefault,
845 IOServiceMatching("IOHIDSystem"));
846 if (!hid_service) {
847 LOG(WARNING) << "Could not obtain IOHIDSystem";
848 return 0;
849 }
850
851 CFTypeRef object = IORegistryEntryCreateCFProperty(hid_service,
852 CFSTR("HIDIdleTime"),
853 kCFAllocatorDefault,
854 0);
855 if (!object) {
856 LOG(WARNING) << "Could not get IOHIDSystem's HIDIdleTime property";
857 IOObjectRelease(hid_service);
858 return 0;
859 }
860
861 int64 idle_time; // in nanoseconds
862 Boolean success = false;
863 if (CFGetTypeID(object) == CFNumberGetTypeID()) {
864 success = CFNumberGetValue((CFNumberRef)object,
865 kCFNumberSInt64Type,
866 &idle_time);
867 } else {
868 LOG(WARNING) << "IOHIDSystem's HIDIdleTime property isn't a number!";
869 }
870
871 CFRelease(object);
872 IOObjectRelease(hid_service);
873
874 if (!success) {
875 LOG(WARNING) << "Could not get IOHIDSystem's HIDIdleTime property's value";
876 return 0;
877 }
878 return idle_time / 1000000; // nano to milli
879 #elif defined(OS_LINUX)
880 if (idle_query_.get())
881 return idle_query_->IdleTime();
882 return 0;
883 #else
884 static bool was_logged = false;
885 if (!was_logged) {
886 was_logged = true;
887 VLOG(1) << "UserIdleTime unimplemented on this platform, synchronization "
888 "will not throttle when user idle";
889 }
890 #endif
891
892 return 0;
893 }
894
895 } // namespace browser_sync
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698