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.
5 #include "sync/sessions/nudge_tracker.h"
7 #include "base/basictypes.h"
8 #include "sync/internal_api/public/engine/polling_constants.h"
9 #include "sync/protocol/sync.pb.h"
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;
23 base::TimeDelta
GetDefaultDelayForType(ModelType model_type
,
24 base::TimeDelta minimum_delay
) {
27 // Accompany types rely on nudges from other types, and hence have long
29 return base::TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds
);
32 // Types with sometimes automatic changes get longer delays to allow more
34 return base::TimeDelta::FromMilliseconds(kSlowNudgeDelayMilliseconds
);
37 case FAVICON_TRACKING
:
38 // Types with navigation triggered changes get longer delays to allow more
40 return base::TimeDelta::FromSeconds(kDefaultSessionsCommitDelaySeconds
);
48 size_t NudgeTracker::kDefaultMaxPayloadsPerType
= 10;
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();
64 type_trackers_
.insert(std::make_pair(it
.Get(), new DataTypeTracker()));
68 NudgeTracker::~NudgeTracker() { }
70 bool NudgeTracker::IsSyncRequired() const {
71 if (IsRetryRequired())
74 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
75 it
!= type_trackers_
.end(); ++it
) {
76 if (it
->second
->IsSyncRequired()) {
84 bool NudgeTracker::IsGetUpdatesRequired() const {
85 if (invalidations_out_of_sync_
)
88 if (IsRetryRequired())
91 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
92 it
!= type_trackers_
.end(); ++it
) {
93 if (it
->second
->IsGetUpdatesRequired()) {
100 bool NudgeTracker::IsRetryRequired() const {
101 if (sync_cycle_start_time_
.is_null())
104 if (current_retry_time_
.is_null())
107 return current_retry_time_
<= sync_cycle_start_time_
;
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();
115 // A successful cycle while invalidations are enabled puts us back into sync.
116 invalidations_out_of_sync_
= !invalidations_enabled_
;
118 for (TypeTrackerMap::iterator it
= type_trackers_
.begin();
119 it
!= type_trackers_
.end(); ++it
) {
120 it
->second
->RecordSuccessfulSyncCycle();
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();
130 TypeTrackerMap::iterator tracker_it
= type_trackers_
.find(type_it
.Get());
131 DCHECK(tracker_it
!= type_trackers_
.end());
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_
);
140 if (type_delay
< delay
)
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();
152 return local_refresh_nudge_delay_
;
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_
;
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();
171 void NudgeTracker::RecordCommitConflict(syncer::ModelType type
) {
172 TypeTrackerMap::iterator tracker_it
= type_trackers_
.find(type
);
173 DCHECK(tracker_it
!= type_trackers_
.end());
174 tracker_it
->second
->RecordCommitConflict();
177 void NudgeTracker::OnInvalidationsEnabled() {
178 invalidations_enabled_
= true;
181 void NudgeTracker::OnInvalidationsDisabled() {
182 invalidations_enabled_
= false;
183 invalidations_out_of_sync_
= true;
186 void NudgeTracker::SetTypesThrottledUntil(
188 base::TimeDelta length
,
189 base::TimeTicks now
) {
190 for (ModelTypeSet::Iterator it
= types
.First(); it
.Good(); it
.Inc()) {
191 TypeTrackerMap::iterator tracker_it
= type_trackers_
.find(it
.Get());
192 tracker_it
->second
->ThrottleType(length
, now
);
196 void NudgeTracker::UpdateTypeThrottlingState(base::TimeTicks now
) {
197 for (TypeTrackerMap::iterator it
= type_trackers_
.begin();
198 it
!= type_trackers_
.end(); ++it
) {
199 it
->second
->UpdateThrottleState(now
);
203 bool NudgeTracker::IsAnyTypeThrottled() const {
204 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
205 it
!= type_trackers_
.end(); ++it
) {
206 if (it
->second
->IsThrottled()) {
213 bool NudgeTracker::IsTypeThrottled(ModelType type
) const {
214 DCHECK(type_trackers_
.find(type
) != type_trackers_
.end());
215 return type_trackers_
.find(type
)->second
->IsThrottled();
218 base::TimeDelta
NudgeTracker::GetTimeUntilNextUnthrottle(
219 base::TimeTicks now
) const {
220 DCHECK(IsAnyTypeThrottled()) << "This function requires a pending unthrottle";
222 // Return min of GetTimeUntilUnthrottle() values for all IsThrottled() types.
223 base::TimeDelta time_until_next_unthrottle
= base::TimeDelta::Max();
224 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
225 it
!= type_trackers_
.end(); ++it
) {
226 if (it
->second
->IsThrottled()) {
227 time_until_next_unthrottle
= std::min(
228 time_until_next_unthrottle
, it
->second
->GetTimeUntilUnthrottle(now
));
231 DCHECK(!time_until_next_unthrottle
.is_max());
233 return time_until_next_unthrottle
;
236 ModelTypeSet
NudgeTracker::GetThrottledTypes() const {
238 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
239 it
!= type_trackers_
.end(); ++it
) {
240 if (it
->second
->IsThrottled()) {
241 result
.Put(it
->first
);
247 ModelTypeSet
NudgeTracker::GetNudgedTypes() const {
249 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
250 it
!= type_trackers_
.end(); ++it
) {
251 if (it
->second
->HasLocalChangePending()) {
252 result
.Put(it
->first
);
258 ModelTypeSet
NudgeTracker::GetNotifiedTypes() const {
260 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
261 it
!= type_trackers_
.end(); ++it
) {
262 if (it
->second
->HasPendingInvalidation()) {
263 result
.Put(it
->first
);
269 ModelTypeSet
NudgeTracker::GetRefreshRequestedTypes() const {
271 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
272 it
!= type_trackers_
.end(); ++it
) {
273 if (it
->second
->HasRefreshRequestPending()) {
274 result
.Put(it
->first
);
280 void NudgeTracker::SetLegacyNotificationHint(
282 sync_pb::DataTypeProgressMarker
* progress
) const {
283 DCHECK(type_trackers_
.find(type
) != type_trackers_
.end());
284 type_trackers_
.find(type
)->second
->SetLegacyNotificationHint(progress
);
287 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource
NudgeTracker::GetLegacySource()
289 // There's an order to these sources: NOTIFICATION, DATATYPE_REFRESH, LOCAL,
290 // RETRY. The server makes optimization decisions based on this field, so
291 // it's important to get this right. Setting it wrong could lead to missed
294 // This complexity is part of the reason why we're deprecating 'source' in
295 // favor of 'origin'.
296 bool has_invalidation_pending
= false;
297 bool has_refresh_request_pending
= false;
298 bool has_commit_pending
= false;
299 bool is_initial_sync_required
= false;
300 bool has_retry
= IsRetryRequired();
302 for (TypeTrackerMap::const_iterator it
= type_trackers_
.begin();
303 it
!= type_trackers_
.end(); ++it
) {
304 const DataTypeTracker
& tracker
= *it
->second
;
305 if (!tracker
.IsThrottled() && tracker
.HasPendingInvalidation()) {
306 has_invalidation_pending
= true;
308 if (!tracker
.IsThrottled() && tracker
.HasRefreshRequestPending()) {
309 has_refresh_request_pending
= true;
311 if (!tracker
.IsThrottled() && tracker
.HasLocalChangePending()) {
312 has_commit_pending
= true;
314 if (!tracker
.IsThrottled() && tracker
.IsInitialSyncRequired()) {
315 is_initial_sync_required
= true;
319 if (has_invalidation_pending
) {
320 return sync_pb::GetUpdatesCallerInfo::NOTIFICATION
;
321 } else if (has_refresh_request_pending
) {
322 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH
;
323 } else if (is_initial_sync_required
) {
324 // Not quite accurate, but good enough for our purposes. This setting of
325 // SOURCE is just a backward-compatibility hack anyway.
326 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH
;
327 } else if (has_commit_pending
) {
328 return sync_pb::GetUpdatesCallerInfo::LOCAL
;
329 } else if (has_retry
) {
330 return sync_pb::GetUpdatesCallerInfo::RETRY
;
332 return sync_pb::GetUpdatesCallerInfo::UNKNOWN
;
336 void NudgeTracker::FillProtoMessage(
338 sync_pb::GetUpdateTriggers
* msg
) const {
339 DCHECK(type_trackers_
.find(type
) != type_trackers_
.end());
341 // Fill what we can from the global data.
342 msg
->set_invalidations_out_of_sync(invalidations_out_of_sync_
);
344 // Delegate the type-specific work to the DataTypeTracker class.
345 type_trackers_
.find(type
)->second
->FillGetUpdatesTriggersMessage(msg
);
348 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now
) {
349 sync_cycle_start_time_
= now
;
351 // If current_retry_time_ is still set, that means we have an old retry time
352 // left over from a previous cycle. For example, maybe we tried to perform
353 // this retry, hit a network connection error, and now we're in exponential
354 // backoff. In that case, we want this sync cycle to include the GU retry
355 // flag so we leave this variable set regardless of whether or not there is an
356 // overwrite pending.
357 if (!current_retry_time_
.is_null()) {
361 // If do not have a current_retry_time_, but we do have a next_retry_time_ and
362 // it is ready to go, then we set it as the current_retry_time_. It will stay
363 // there until a GU retry has succeeded.
364 if (!next_retry_time_
.is_null() &&
365 next_retry_time_
<= sync_cycle_start_time_
) {
366 current_retry_time_
= next_retry_time_
;
367 next_retry_time_
= base::TimeTicks();
371 void NudgeTracker::SetHintBufferSize(size_t size
) {
372 for (TypeTrackerMap::iterator it
= type_trackers_
.begin();
373 it
!= type_trackers_
.end(); ++it
) {
374 it
->second
->UpdatePayloadBufferSize(size
);
378 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time
) {
379 next_retry_time_
= retry_time
;
382 void NudgeTracker::OnReceivedCustomNudgeDelays(
383 const std::map
<ModelType
, base::TimeDelta
>& delay_map
) {
384 for (std::map
<ModelType
, base::TimeDelta
>::const_iterator iter
=
386 iter
!= delay_map
.end();
388 ModelType type
= iter
->first
;
389 DCHECK(syncer::ProtocolTypes().Has(type
));
390 TypeTrackerMap::iterator type_iter
= type_trackers_
.find(type
);
391 if (type_iter
== type_trackers_
.end())
394 if (iter
->second
> minimum_local_nudge_delay_
) {
395 type_iter
->second
->UpdateLocalNudgeDelay(iter
->second
);
397 type_iter
->second
->UpdateLocalNudgeDelay(
398 GetDefaultDelayForType(type
,
399 minimum_local_nudge_delay_
));
404 void NudgeTracker::SetDefaultNudgeDelay(base::TimeDelta nudge_delay
) {
405 minimum_local_nudge_delay_
= nudge_delay
;
408 } // namespace sessions
409 } // namespace syncer