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