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 "components/sync/engine_impl/cycle/nudge_tracker.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "components/sync/base/sync_base_switches.h"
11 #include "components/sync/engine/polling_constants.h"
12
13 namespace syncer {
14
15 namespace {
16
17 // Delays for syncer nudges.
18 const int kDefaultNudgeDelayMilliseconds = 200;
19 const int kSlowNudgeDelayMilliseconds = 2000;
20 const int kSyncRefreshDelayMilliseconds = 500;
21 const int kSyncSchedulerDelayMilliseconds = 250;
22
GetSharingMessageDelay(base::TimeDelta default_delay)23 base::TimeDelta GetSharingMessageDelay(base::TimeDelta default_delay) {
24 if (!base::FeatureList::IsEnabled(
25 switches::kSyncCustomSharingMessageNudgeDelay)) {
26 return default_delay;
27 }
28
29 return base::TimeDelta::FromMilliseconds(
30 switches::kSyncSharingMessageNudgeDelayMilliseconds.Get());
31 }
32
GetDefaultDelayForType(ModelType model_type,base::TimeDelta minimum_delay)33 base::TimeDelta GetDefaultDelayForType(ModelType model_type,
34 base::TimeDelta minimum_delay) {
35 switch (model_type) {
36 case AUTOFILL:
37 case USER_EVENTS:
38 // Accompany types rely on nudges from other types, and hence have long
39 // nudge delays.
40 return base::TimeDelta::FromSeconds(kDefaultPollIntervalSeconds);
41 case BOOKMARKS:
42 case PREFERENCES:
43 case SESSIONS:
44 // Types with sometimes automatic changes get longer delays to allow more
45 // coalescing.
46 return base::TimeDelta::FromMilliseconds(kSlowNudgeDelayMilliseconds);
47 case SHARING_MESSAGE:
48 return GetSharingMessageDelay(minimum_delay);
49 default:
50 return minimum_delay;
51 }
52 }
53
54 } // namespace
55
NudgeTracker()56 NudgeTracker::NudgeTracker()
57 : invalidations_enabled_(false),
58 invalidations_out_of_sync_(true),
59 minimum_local_nudge_delay_(
60 base::TimeDelta::FromMilliseconds(kDefaultNudgeDelayMilliseconds)),
61 local_refresh_nudge_delay_(
62 base::TimeDelta::FromMilliseconds(kSyncRefreshDelayMilliseconds)),
63 remote_invalidation_nudge_delay_(
64 base::TimeDelta::FromMilliseconds(kSyncSchedulerDelayMilliseconds)) {
65 // Default initialize all the type trackers.
66 for (ModelType type : ProtocolTypes()) {
67 type_trackers_.emplace(
68 type, std::make_unique<DataTypeTracker>(kDefaultMaxPayloadsPerType));
69 }
70 }
71
~NudgeTracker()72 NudgeTracker::~NudgeTracker() {}
73
IsSyncRequired(ModelTypeSet types) const74 bool NudgeTracker::IsSyncRequired(ModelTypeSet types) const {
75 if (IsRetryRequired()) {
76 return true;
77 }
78
79 for (ModelType type : types) {
80 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
81 DCHECK(tracker_it != type_trackers_.end()) << ModelTypeToString(type);
82 if (tracker_it->second->IsSyncRequired()) {
83 return true;
84 }
85 }
86
87 return false;
88 }
89
IsGetUpdatesRequired(ModelTypeSet types) const90 bool NudgeTracker::IsGetUpdatesRequired(ModelTypeSet types) const {
91 if (invalidations_out_of_sync_) {
92 return true;
93 }
94
95 if (IsRetryRequired()) {
96 return true;
97 }
98
99 for (ModelType type : types) {
100 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
101 DCHECK(tracker_it != type_trackers_.end()) << ModelTypeToString(type);
102 if (tracker_it->second->IsGetUpdatesRequired()) {
103 return true;
104 }
105 }
106 return false;
107 }
108
IsRetryRequired() const109 bool NudgeTracker::IsRetryRequired() const {
110 if (sync_cycle_start_time_.is_null()) {
111 return false;
112 }
113
114 if (current_retry_time_.is_null()) {
115 return false;
116 }
117
118 return current_retry_time_ <= sync_cycle_start_time_;
119 }
120
RecordSuccessfulSyncCycle(ModelTypeSet types)121 void NudgeTracker::RecordSuccessfulSyncCycle(ModelTypeSet types) {
122 // If a retry was required, we've just serviced it. Unset the flag.
123 if (IsRetryRequired()) {
124 current_retry_time_ = base::TimeTicks();
125 }
126
127 // A successful cycle while invalidations are enabled puts us back into sync.
128 invalidations_out_of_sync_ = !invalidations_enabled_;
129
130 for (ModelType type : types) {
131 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
132 DCHECK(tracker_it != type_trackers_.end()) << ModelTypeToString(type);
133 tracker_it->second->RecordSuccessfulSyncCycle();
134 }
135 }
136
RecordInitialSyncDone(ModelTypeSet types)137 void NudgeTracker::RecordInitialSyncDone(ModelTypeSet types) {
138 for (ModelType type : types) {
139 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
140 DCHECK(tracker_it != type_trackers_.end()) << ModelTypeToString(type);
141 tracker_it->second->RecordInitialSyncDone();
142 }
143 }
144
RecordLocalChange(ModelTypeSet types)145 base::TimeDelta NudgeTracker::RecordLocalChange(ModelTypeSet types) {
146 // Start with the longest delay.
147 base::TimeDelta delay =
148 base::TimeDelta::FromSeconds(kDefaultPollIntervalSeconds);
149 for (ModelType type : types) {
150 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
151 DCHECK(tracker_it != type_trackers_.end());
152
153 // Only if the type tracker has a valid delay (non-zero) that is shorter
154 // than the calculated delay do we update the calculated delay.
155 base::TimeDelta type_delay = tracker_it->second->RecordLocalChange();
156 if (type_delay.is_zero()) {
157 type_delay = GetDefaultDelayForType(type, minimum_local_nudge_delay_);
158 }
159 if (type_delay < delay) {
160 delay = type_delay;
161 }
162 }
163 return delay;
164 }
165
RecordLocalRefreshRequest(ModelTypeSet types)166 base::TimeDelta NudgeTracker::RecordLocalRefreshRequest(ModelTypeSet types) {
167 for (ModelType type : types) {
168 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
169 DCHECK(tracker_it != type_trackers_.end()) << ModelTypeToString(type);
170 tracker_it->second->RecordLocalRefreshRequest();
171 }
172 return local_refresh_nudge_delay_;
173 }
174
RecordRemoteInvalidation(ModelType type,std::unique_ptr<InvalidationInterface> invalidation)175 base::TimeDelta NudgeTracker::RecordRemoteInvalidation(
176 ModelType type,
177 std::unique_ptr<InvalidationInterface> invalidation) {
178 // Forward the invalidations to the proper recipient.
179 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
180 DCHECK(tracker_it != type_trackers_.end());
181 tracker_it->second->RecordRemoteInvalidation(std::move(invalidation));
182 return remote_invalidation_nudge_delay_;
183 }
184
RecordInitialSyncRequired(ModelType type)185 void NudgeTracker::RecordInitialSyncRequired(ModelType type) {
186 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
187 DCHECK(tracker_it != type_trackers_.end());
188 tracker_it->second->RecordInitialSyncRequired();
189 }
190
RecordCommitConflict(ModelType type)191 void NudgeTracker::RecordCommitConflict(ModelType type) {
192 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
193 DCHECK(tracker_it != type_trackers_.end());
194 tracker_it->second->RecordCommitConflict();
195 }
196
OnInvalidationsEnabled()197 void NudgeTracker::OnInvalidationsEnabled() {
198 invalidations_enabled_ = true;
199 }
200
OnInvalidationsDisabled()201 void NudgeTracker::OnInvalidationsDisabled() {
202 invalidations_enabled_ = false;
203 invalidations_out_of_sync_ = true;
204 }
205
SetTypesThrottledUntil(ModelTypeSet types,base::TimeDelta length,base::TimeTicks now)206 void NudgeTracker::SetTypesThrottledUntil(ModelTypeSet types,
207 base::TimeDelta length,
208 base::TimeTicks now) {
209 for (ModelType type : types) {
210 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
211 tracker_it->second->ThrottleType(length, now);
212 }
213 }
214
SetTypeBackedOff(ModelType type,base::TimeDelta length,base::TimeTicks now)215 void NudgeTracker::SetTypeBackedOff(ModelType type,
216 base::TimeDelta length,
217 base::TimeTicks now) {
218 TypeTrackerMap::const_iterator tracker_it = type_trackers_.find(type);
219 DCHECK(tracker_it != type_trackers_.end());
220 tracker_it->second->BackOffType(length, now);
221 }
222
UpdateTypeThrottlingAndBackoffState()223 void NudgeTracker::UpdateTypeThrottlingAndBackoffState() {
224 for (const auto& type_and_tracker : type_trackers_) {
225 type_and_tracker.second->UpdateThrottleOrBackoffState();
226 }
227 }
228
IsAnyTypeBlocked() const229 bool NudgeTracker::IsAnyTypeBlocked() const {
230 for (const auto& type_and_tracker : type_trackers_) {
231 if (type_and_tracker.second->IsBlocked()) {
232 return true;
233 }
234 }
235 return false;
236 }
237
IsTypeBlocked(ModelType type) const238 bool NudgeTracker::IsTypeBlocked(ModelType type) const {
239 DCHECK(type_trackers_.find(type) != type_trackers_.end())
240 << ModelTypeToString(type);
241 return type_trackers_.find(type)->second->IsBlocked();
242 }
243
GetTypeBlockingMode(ModelType type) const244 WaitInterval::BlockingMode NudgeTracker::GetTypeBlockingMode(
245 ModelType type) const {
246 DCHECK(type_trackers_.find(type) != type_trackers_.end());
247 return type_trackers_.find(type)->second->GetBlockingMode();
248 }
249
GetTimeUntilNextUnblock() const250 base::TimeDelta NudgeTracker::GetTimeUntilNextUnblock() const {
251 DCHECK(IsAnyTypeBlocked()) << "This function requires a pending unblock.";
252
253 // Return min of GetTimeUntilUnblock() values for all IsBlocked() types.
254 base::TimeDelta time_until_next_unblock = base::TimeDelta::Max();
255 for (const auto& type_and_tracker : type_trackers_) {
256 if (type_and_tracker.second->IsBlocked()) {
257 time_until_next_unblock =
258 std::min(time_until_next_unblock,
259 type_and_tracker.second->GetTimeUntilUnblock());
260 }
261 }
262 DCHECK(!time_until_next_unblock.is_max());
263
264 return time_until_next_unblock;
265 }
266
GetTypeLastBackoffInterval(ModelType type) const267 base::TimeDelta NudgeTracker::GetTypeLastBackoffInterval(ModelType type) const {
268 auto tracker_it = type_trackers_.find(type);
269 DCHECK(tracker_it != type_trackers_.end());
270
271 return tracker_it->second->GetLastBackoffInterval();
272 }
273
GetBlockedTypes() const274 ModelTypeSet NudgeTracker::GetBlockedTypes() const {
275 ModelTypeSet result;
276 for (const auto& type_and_tracker : type_trackers_) {
277 if (type_and_tracker.second->IsBlocked()) {
278 result.Put(type_and_tracker.first);
279 }
280 }
281 return result;
282 }
283
GetNudgedTypes() const284 ModelTypeSet NudgeTracker::GetNudgedTypes() const {
285 ModelTypeSet result;
286 for (const auto& type_and_tracker : type_trackers_) {
287 if (type_and_tracker.second->HasLocalChangePending()) {
288 result.Put(type_and_tracker.first);
289 }
290 }
291 return result;
292 }
293
GetNotifiedTypes() const294 ModelTypeSet NudgeTracker::GetNotifiedTypes() const {
295 ModelTypeSet result;
296 for (const auto& type_and_tracker : type_trackers_) {
297 if (type_and_tracker.second->HasPendingInvalidation()) {
298 result.Put(type_and_tracker.first);
299 }
300 }
301 return result;
302 }
303
GetRefreshRequestedTypes() const304 ModelTypeSet NudgeTracker::GetRefreshRequestedTypes() const {
305 ModelTypeSet result;
306 for (const auto& type_and_tracker : type_trackers_) {
307 if (type_and_tracker.second->HasRefreshRequestPending()) {
308 result.Put(type_and_tracker.first);
309 }
310 }
311 return result;
312 }
313
SetLegacyNotificationHint(ModelType type,sync_pb::DataTypeProgressMarker * progress) const314 void NudgeTracker::SetLegacyNotificationHint(
315 ModelType type,
316 sync_pb::DataTypeProgressMarker* progress) const {
317 DCHECK(type_trackers_.find(type) != type_trackers_.end());
318 type_trackers_.find(type)->second->SetLegacyNotificationHint(progress);
319 }
320
GetOrigin() const321 sync_pb::SyncEnums::GetUpdatesOrigin NudgeTracker::GetOrigin() const {
322 for (const auto& type_and_tracker : type_trackers_) {
323 const DataTypeTracker& tracker = *type_and_tracker.second;
324 if (!tracker.IsBlocked() &&
325 (tracker.HasPendingInvalidation() ||
326 tracker.HasRefreshRequestPending() ||
327 tracker.HasLocalChangePending() || tracker.IsInitialSyncRequired())) {
328 return sync_pb::SyncEnums::GU_TRIGGER;
329 }
330 }
331
332 if (IsRetryRequired()) {
333 return sync_pb::SyncEnums::RETRY;
334 }
335
336 return sync_pb::SyncEnums::UNKNOWN_ORIGIN;
337 }
338
FillProtoMessage(ModelType type,sync_pb::GetUpdateTriggers * msg) const339 void NudgeTracker::FillProtoMessage(ModelType type,
340 sync_pb::GetUpdateTriggers* msg) const {
341 DCHECK(type_trackers_.find(type) != type_trackers_.end());
342
343 // Fill what we can from the global data.
344 msg->set_invalidations_out_of_sync(invalidations_out_of_sync_);
345
346 // Delegate the type-specific work to the DataTypeTracker class.
347 type_trackers_.find(type)->second->FillGetUpdatesTriggersMessage(msg);
348 }
349
SetSyncCycleStartTime(base::TimeTicks now)350 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now) {
351 sync_cycle_start_time_ = now;
352
353 // If current_retry_time_ is still set, that means we have an old retry time
354 // left over from a previous cycle. For example, maybe we tried to perform
355 // this retry, hit a network connection error, and now we're in exponential
356 // backoff. In that case, we want this sync cycle to include the GU retry
357 // flag so we leave this variable set regardless of whether or not there is an
358 // overwrite pending.
359 if (!current_retry_time_.is_null()) {
360 return;
361 }
362
363 // If do not have a current_retry_time_, but we do have a next_retry_time_ and
364 // it is ready to go, then we set it as the current_retry_time_. It will stay
365 // there until a GU retry has succeeded.
366 if (!next_retry_time_.is_null() &&
367 next_retry_time_ <= sync_cycle_start_time_) {
368 current_retry_time_ = next_retry_time_;
369 next_retry_time_ = base::TimeTicks();
370 }
371 }
372
SetHintBufferSize(size_t size)373 void NudgeTracker::SetHintBufferSize(size_t size) {
374 for (const auto& type_and_tracker : type_trackers_) {
375 type_and_tracker.second->UpdatePayloadBufferSize(size);
376 }
377 }
378
SetNextRetryTime(base::TimeTicks retry_time)379 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time) {
380 next_retry_time_ = retry_time;
381 }
382
OnReceivedCustomNudgeDelays(const std::map<ModelType,base::TimeDelta> & delay_map)383 void NudgeTracker::OnReceivedCustomNudgeDelays(
384 const std::map<ModelType, base::TimeDelta>& delay_map) {
385 for (const auto& type_and_delay : delay_map) {
386 ModelType type = type_and_delay.first;
387 base::TimeDelta delay = type_and_delay.second;
388 DCHECK(ProtocolTypes().Has(type));
389 TypeTrackerMap::const_iterator type_iter = type_trackers_.find(type);
390 if (type_iter == type_trackers_.end()) {
391 continue;
392 }
393 DataTypeTracker* type_tracker = type_iter->second.get();
394
395 if (delay > minimum_local_nudge_delay_) {
396 type_tracker->UpdateLocalNudgeDelay(delay);
397 } else {
398 type_tracker->UpdateLocalNudgeDelay(
399 GetDefaultDelayForType(type, minimum_local_nudge_delay_));
400 }
401 }
402 }
403
SetDefaultNudgeDelay(base::TimeDelta nudge_delay)404 void NudgeTracker::SetDefaultNudgeDelay(base::TimeDelta nudge_delay) {
405 minimum_local_nudge_delay_ = nudge_delay;
406 }
407
408 } // namespace syncer
409