1 // Copyright (c) 2013 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/sessions/nudge_tracker.h"
6
7 #include "base/basictypes.h"
8 #include "sync/internal_api/public/engine/polling_constants.h"
9 #include "sync/protocol/sync.pb.h"
10
11 namespace syncer {
12 namespace sessions {
13
14 namespace {
15
16 // Delays for syncer nudges.
17 const int kDefaultNudgeDelayMilliseconds = 200;
18 const int kSlowNudgeDelayMilliseconds = 2000;
19 const int kDefaultSessionsCommitDelaySeconds = 10;
20 const int kSyncRefreshDelayMilliseconds = 500;
21 const int kSyncSchedulerDelayMilliseconds = 250;
22
GetDefaultDelayForType(ModelType model_type,base::TimeDelta minimum_delay)23 base::TimeDelta GetDefaultDelayForType(ModelType model_type,
24 base::TimeDelta minimum_delay) {
25 switch (model_type) {
26 case AUTOFILL:
27 // Accompany types rely on nudges from other types, and hence have long
28 // nudge delays.
29 return base::TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds);
30 case BOOKMARKS:
31 case PREFERENCES:
32 // Types with sometimes automatic changes get longer delays to allow more
33 // coalescing.
34 return base::TimeDelta::FromMilliseconds(kSlowNudgeDelayMilliseconds);
35 case SESSIONS:
36 case FAVICON_IMAGES:
37 case FAVICON_TRACKING:
38 // Types with navigation triggered changes get longer delays to allow more
39 // coalescing.
40 return base::TimeDelta::FromSeconds(kDefaultSessionsCommitDelaySeconds);
41 default:
42 return minimum_delay;
43 }
44 }
45
46 } // namespace
47
48 size_t NudgeTracker::kDefaultMaxPayloadsPerType = 10;
49
NudgeTracker()50 NudgeTracker::NudgeTracker()
51 : type_tracker_deleter_(&type_trackers_),
52 invalidations_enabled_(false),
53 invalidations_out_of_sync_(true),
54 minimum_local_nudge_delay_(
55 base::TimeDelta::FromMilliseconds(kDefaultNudgeDelayMilliseconds)),
56 local_refresh_nudge_delay_(
57 base::TimeDelta::FromMilliseconds(kSyncRefreshDelayMilliseconds)),
58 remote_invalidation_nudge_delay_(
59 base::TimeDelta::FromMilliseconds(kSyncSchedulerDelayMilliseconds)) {
60 ModelTypeSet protocol_types = ProtocolTypes();
61 // Default initialize all the type trackers.
62 for (ModelTypeSet::Iterator it = protocol_types.First(); it.Good();
63 it.Inc()) {
64 type_trackers_.insert(std::make_pair(it.Get(), new DataTypeTracker()));
65 }
66 }
67
~NudgeTracker()68 NudgeTracker::~NudgeTracker() { }
69
IsSyncRequired() const70 bool NudgeTracker::IsSyncRequired() const {
71 if (IsRetryRequired())
72 return true;
73
74 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
75 it != type_trackers_.end(); ++it) {
76 if (it->second->IsSyncRequired()) {
77 return true;
78 }
79 }
80
81 return false;
82 }
83
IsGetUpdatesRequired() const84 bool NudgeTracker::IsGetUpdatesRequired() const {
85 if (invalidations_out_of_sync_)
86 return true;
87
88 if (IsRetryRequired())
89 return true;
90
91 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
92 it != type_trackers_.end(); ++it) {
93 if (it->second->IsGetUpdatesRequired()) {
94 return true;
95 }
96 }
97 return false;
98 }
99
IsRetryRequired() const100 bool NudgeTracker::IsRetryRequired() const {
101 if (sync_cycle_start_time_.is_null())
102 return false;
103
104 if (current_retry_time_.is_null())
105 return false;
106
107 return current_retry_time_ <= sync_cycle_start_time_;
108 }
109
RecordSuccessfulSyncCycle()110 void NudgeTracker::RecordSuccessfulSyncCycle() {
111 // If a retry was required, we've just serviced it. Unset the flag.
112 if (IsRetryRequired())
113 current_retry_time_ = base::TimeTicks();
114
115 // A successful cycle while invalidations are enabled puts us back into sync.
116 invalidations_out_of_sync_ = !invalidations_enabled_;
117
118 for (TypeTrackerMap::iterator it = type_trackers_.begin();
119 it != type_trackers_.end(); ++it) {
120 it->second->RecordSuccessfulSyncCycle();
121 }
122 }
123
RecordLocalChange(ModelTypeSet types)124 base::TimeDelta NudgeTracker::RecordLocalChange(ModelTypeSet types) {
125 // Start with the longest delay.
126 base::TimeDelta delay =
127 base::TimeDelta::FromMilliseconds(kDefaultShortPollIntervalSeconds);
128 for (ModelTypeSet::Iterator type_it = types.First(); type_it.Good();
129 type_it.Inc()) {
130 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type_it.Get());
131 DCHECK(tracker_it != type_trackers_.end());
132
133 // Only if the type tracker has a valid delay (non-zero) that is shorter
134 // than the calculated delay do we update the calculated delay.
135 base::TimeDelta type_delay = tracker_it->second->RecordLocalChange();
136 if (type_delay == base::TimeDelta()) {
137 type_delay = GetDefaultDelayForType(type_it.Get(),
138 minimum_local_nudge_delay_);
139 }
140 if (type_delay < delay)
141 delay = type_delay;
142 }
143 return delay;
144 }
145
RecordLocalRefreshRequest(ModelTypeSet types)146 base::TimeDelta NudgeTracker::RecordLocalRefreshRequest(ModelTypeSet types) {
147 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) {
148 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get());
149 DCHECK(tracker_it != type_trackers_.end());
150 tracker_it->second->RecordLocalRefreshRequest();
151 }
152 return local_refresh_nudge_delay_;
153 }
154
RecordRemoteInvalidation(syncer::ModelType type,scoped_ptr<InvalidationInterface> invalidation)155 base::TimeDelta NudgeTracker::RecordRemoteInvalidation(
156 syncer::ModelType type,
157 scoped_ptr<InvalidationInterface> invalidation) {
158 // Forward the invalidations to the proper recipient.
159 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type);
160 DCHECK(tracker_it != type_trackers_.end());
161 tracker_it->second->RecordRemoteInvalidation(invalidation.Pass());
162 return remote_invalidation_nudge_delay_;
163 }
164
RecordInitialSyncRequired(syncer::ModelType type)165 void NudgeTracker::RecordInitialSyncRequired(syncer::ModelType type) {
166 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type);
167 DCHECK(tracker_it != type_trackers_.end());
168 tracker_it->second->RecordInitialSyncRequired();
169 }
170
OnInvalidationsEnabled()171 void NudgeTracker::OnInvalidationsEnabled() {
172 invalidations_enabled_ = true;
173 }
174
OnInvalidationsDisabled()175 void NudgeTracker::OnInvalidationsDisabled() {
176 invalidations_enabled_ = false;
177 invalidations_out_of_sync_ = true;
178 }
179
SetTypesThrottledUntil(ModelTypeSet types,base::TimeDelta length,base::TimeTicks now)180 void NudgeTracker::SetTypesThrottledUntil(
181 ModelTypeSet types,
182 base::TimeDelta length,
183 base::TimeTicks now) {
184 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) {
185 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get());
186 tracker_it->second->ThrottleType(length, now);
187 }
188 }
189
UpdateTypeThrottlingState(base::TimeTicks now)190 void NudgeTracker::UpdateTypeThrottlingState(base::TimeTicks now) {
191 for (TypeTrackerMap::iterator it = type_trackers_.begin();
192 it != type_trackers_.end(); ++it) {
193 it->second->UpdateThrottleState(now);
194 }
195 }
196
IsAnyTypeThrottled() const197 bool NudgeTracker::IsAnyTypeThrottled() const {
198 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
199 it != type_trackers_.end(); ++it) {
200 if (it->second->IsThrottled()) {
201 return true;
202 }
203 }
204 return false;
205 }
206
IsTypeThrottled(ModelType type) const207 bool NudgeTracker::IsTypeThrottled(ModelType type) const {
208 DCHECK(type_trackers_.find(type) != type_trackers_.end());
209 return type_trackers_.find(type)->second->IsThrottled();
210 }
211
GetTimeUntilNextUnthrottle(base::TimeTicks now) const212 base::TimeDelta NudgeTracker::GetTimeUntilNextUnthrottle(
213 base::TimeTicks now) const {
214 DCHECK(IsAnyTypeThrottled()) << "This function requires a pending unthrottle";
215
216 // Return min of GetTimeUntilUnthrottle() values for all IsThrottled() types.
217 base::TimeDelta time_until_next_unthrottle = base::TimeDelta::Max();
218 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
219 it != type_trackers_.end(); ++it) {
220 if (it->second->IsThrottled()) {
221 time_until_next_unthrottle = std::min(
222 time_until_next_unthrottle, it->second->GetTimeUntilUnthrottle(now));
223 }
224 }
225 DCHECK(!time_until_next_unthrottle.is_max());
226
227 return time_until_next_unthrottle;
228 }
229
GetThrottledTypes() const230 ModelTypeSet NudgeTracker::GetThrottledTypes() const {
231 ModelTypeSet result;
232 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
233 it != type_trackers_.end(); ++it) {
234 if (it->second->IsThrottled()) {
235 result.Put(it->first);
236 }
237 }
238 return result;
239 }
240
GetNudgedTypes() const241 ModelTypeSet NudgeTracker::GetNudgedTypes() const {
242 ModelTypeSet result;
243 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
244 it != type_trackers_.end(); ++it) {
245 if (it->second->HasLocalChangePending()) {
246 result.Put(it->first);
247 }
248 }
249 return result;
250 }
251
GetNotifiedTypes() const252 ModelTypeSet NudgeTracker::GetNotifiedTypes() const {
253 ModelTypeSet result;
254 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
255 it != type_trackers_.end(); ++it) {
256 if (it->second->HasPendingInvalidation()) {
257 result.Put(it->first);
258 }
259 }
260 return result;
261 }
262
GetRefreshRequestedTypes() const263 ModelTypeSet NudgeTracker::GetRefreshRequestedTypes() const {
264 ModelTypeSet result;
265 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
266 it != type_trackers_.end(); ++it) {
267 if (it->second->HasRefreshRequestPending()) {
268 result.Put(it->first);
269 }
270 }
271 return result;
272 }
273
SetLegacyNotificationHint(ModelType type,sync_pb::DataTypeProgressMarker * progress) const274 void NudgeTracker::SetLegacyNotificationHint(
275 ModelType type,
276 sync_pb::DataTypeProgressMarker* progress) const {
277 DCHECK(type_trackers_.find(type) != type_trackers_.end());
278 type_trackers_.find(type)->second->SetLegacyNotificationHint(progress);
279 }
280
GetLegacySource() const281 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource NudgeTracker::GetLegacySource()
282 const {
283 // There's an order to these sources: NOTIFICATION, DATATYPE_REFRESH, LOCAL,
284 // RETRY. The server makes optimization decisions based on this field, so
285 // it's important to get this right. Setting it wrong could lead to missed
286 // updates.
287 //
288 // This complexity is part of the reason why we're deprecating 'source' in
289 // favor of 'origin'.
290 bool has_invalidation_pending = false;
291 bool has_refresh_request_pending = false;
292 bool has_commit_pending = false;
293 bool is_initial_sync_required = false;
294 bool has_retry = IsRetryRequired();
295
296 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
297 it != type_trackers_.end(); ++it) {
298 const DataTypeTracker& tracker = *it->second;
299 if (!tracker.IsThrottled() && tracker.HasPendingInvalidation()) {
300 has_invalidation_pending = true;
301 }
302 if (!tracker.IsThrottled() && tracker.HasRefreshRequestPending()) {
303 has_refresh_request_pending = true;
304 }
305 if (!tracker.IsThrottled() && tracker.HasLocalChangePending()) {
306 has_commit_pending = true;
307 }
308 if (!tracker.IsThrottled() && tracker.IsInitialSyncRequired()) {
309 is_initial_sync_required = true;
310 }
311 }
312
313 if (has_invalidation_pending) {
314 return sync_pb::GetUpdatesCallerInfo::NOTIFICATION;
315 } else if (has_refresh_request_pending) {
316 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH;
317 } else if (is_initial_sync_required) {
318 // Not quite accurate, but good enough for our purposes. This setting of
319 // SOURCE is just a backward-compatibility hack anyway.
320 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH;
321 } else if (has_commit_pending) {
322 return sync_pb::GetUpdatesCallerInfo::LOCAL;
323 } else if (has_retry) {
324 return sync_pb::GetUpdatesCallerInfo::RETRY;
325 } else {
326 return sync_pb::GetUpdatesCallerInfo::UNKNOWN;
327 }
328 }
329
FillProtoMessage(ModelType type,sync_pb::GetUpdateTriggers * msg) const330 void NudgeTracker::FillProtoMessage(
331 ModelType type,
332 sync_pb::GetUpdateTriggers* msg) const {
333 DCHECK(type_trackers_.find(type) != type_trackers_.end());
334
335 // Fill what we can from the global data.
336 msg->set_invalidations_out_of_sync(invalidations_out_of_sync_);
337
338 // Delegate the type-specific work to the DataTypeTracker class.
339 type_trackers_.find(type)->second->FillGetUpdatesTriggersMessage(msg);
340 }
341
SetSyncCycleStartTime(base::TimeTicks now)342 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now) {
343 sync_cycle_start_time_ = now;
344
345 // If current_retry_time_ is still set, that means we have an old retry time
346 // left over from a previous cycle. For example, maybe we tried to perform
347 // this retry, hit a network connection error, and now we're in exponential
348 // backoff. In that case, we want this sync cycle to include the GU retry
349 // flag so we leave this variable set regardless of whether or not there is an
350 // overwrite pending.
351 if (!current_retry_time_.is_null()) {
352 return;
353 }
354
355 // If do not have a current_retry_time_, but we do have a next_retry_time_ and
356 // it is ready to go, then we set it as the current_retry_time_. It will stay
357 // there until a GU retry has succeeded.
358 if (!next_retry_time_.is_null() &&
359 next_retry_time_ <= sync_cycle_start_time_) {
360 current_retry_time_ = next_retry_time_;
361 next_retry_time_ = base::TimeTicks();
362 }
363 }
364
SetHintBufferSize(size_t size)365 void NudgeTracker::SetHintBufferSize(size_t size) {
366 for (TypeTrackerMap::iterator it = type_trackers_.begin();
367 it != type_trackers_.end(); ++it) {
368 it->second->UpdatePayloadBufferSize(size);
369 }
370 }
371
SetNextRetryTime(base::TimeTicks retry_time)372 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time) {
373 next_retry_time_ = retry_time;
374 }
375
OnReceivedCustomNudgeDelays(const std::map<ModelType,base::TimeDelta> & delay_map)376 void NudgeTracker::OnReceivedCustomNudgeDelays(
377 const std::map<ModelType, base::TimeDelta>& delay_map) {
378 for (std::map<ModelType, base::TimeDelta>::const_iterator iter =
379 delay_map.begin();
380 iter != delay_map.end();
381 ++iter) {
382 ModelType type = iter->first;
383 DCHECK(syncer::ProtocolTypes().Has(type));
384 TypeTrackerMap::iterator type_iter = type_trackers_.find(type);
385 if (type_iter == type_trackers_.end())
386 continue;
387
388 if (iter->second > minimum_local_nudge_delay_) {
389 type_iter->second->UpdateLocalNudgeDelay(iter->second);
390 } else {
391 type_iter->second->UpdateLocalNudgeDelay(
392 GetDefaultDelayForType(type,
393 minimum_local_nudge_delay_));
394 }
395 }
396 }
397
SetDefaultNudgeDelay(base::TimeDelta nudge_delay)398 void NudgeTracker::SetDefaultNudgeDelay(base::TimeDelta nudge_delay) {
399 minimum_local_nudge_delay_ = nudge_delay;
400 }
401
402 } // namespace sessions
403 } // namespace syncer
404