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/base/invalidation.h"
9 #include "sync/notifier/invalidation_util.h"
10 #include "sync/notifier/object_id_invalidation_map.h"
11 #include "sync/protocol/sync.pb.h"
12
13 namespace syncer {
14 namespace sessions {
15
16 size_t NudgeTracker::kDefaultMaxPayloadsPerType = 10;
17
NudgeTracker()18 NudgeTracker::NudgeTracker()
19 : invalidations_enabled_(false),
20 invalidations_out_of_sync_(true) {
21 ModelTypeSet protocol_types = ProtocolTypes();
22 // Default initialize all the type trackers.
23 for (ModelTypeSet::Iterator it = protocol_types.First(); it.Good();
24 it.Inc()) {
25 invalidation::ObjectId id;
26 if (!RealModelTypeToObjectId(it.Get(), &id)) {
27 NOTREACHED();
28 } else {
29 type_trackers_.insert(std::make_pair(it.Get(), DataTypeTracker(id)));
30 }
31 }
32 }
33
~NudgeTracker()34 NudgeTracker::~NudgeTracker() { }
35
IsSyncRequired() const36 bool NudgeTracker::IsSyncRequired() const {
37 if (IsRetryRequired())
38 return true;
39
40 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
41 it != type_trackers_.end(); ++it) {
42 if (it->second.IsSyncRequired()) {
43 return true;
44 }
45 }
46
47 return false;
48 }
49
IsGetUpdatesRequired() const50 bool NudgeTracker::IsGetUpdatesRequired() const {
51 if (invalidations_out_of_sync_)
52 return true;
53
54 if (IsRetryRequired())
55 return true;
56
57 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
58 it != type_trackers_.end(); ++it) {
59 if (it->second.IsGetUpdatesRequired()) {
60 return true;
61 }
62 }
63 return false;
64 }
65
IsRetryRequired() const66 bool NudgeTracker::IsRetryRequired() const {
67 if (sync_cycle_start_time_.is_null())
68 return false;
69
70 if (current_retry_time_.is_null())
71 return false;
72
73 return current_retry_time_ < sync_cycle_start_time_;
74 }
75
RecordSuccessfulSyncCycle()76 void NudgeTracker::RecordSuccessfulSyncCycle() {
77 // If a retry was required, we've just serviced it. Unset the flag.
78 if (IsRetryRequired())
79 current_retry_time_ = base::TimeTicks();
80
81 // A successful cycle while invalidations are enabled puts us back into sync.
82 invalidations_out_of_sync_ = !invalidations_enabled_;
83
84 for (TypeTrackerMap::iterator it = type_trackers_.begin();
85 it != type_trackers_.end(); ++it) {
86 it->second.RecordSuccessfulSyncCycle();
87 }
88 }
89
RecordLocalChange(ModelTypeSet types)90 void NudgeTracker::RecordLocalChange(ModelTypeSet types) {
91 for (ModelTypeSet::Iterator type_it = types.First(); type_it.Good();
92 type_it.Inc()) {
93 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type_it.Get());
94 DCHECK(tracker_it != type_trackers_.end());
95 tracker_it->second.RecordLocalChange();
96 }
97 }
98
RecordLocalRefreshRequest(ModelTypeSet types)99 void NudgeTracker::RecordLocalRefreshRequest(ModelTypeSet types) {
100 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) {
101 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get());
102 DCHECK(tracker_it != type_trackers_.end());
103 tracker_it->second.RecordLocalRefreshRequest();
104 }
105 }
106
RecordRemoteInvalidation(const ObjectIdInvalidationMap & invalidation_map)107 void NudgeTracker::RecordRemoteInvalidation(
108 const ObjectIdInvalidationMap& invalidation_map) {
109 // Be very careful here. The invalidations acknowledgement system requires a
110 // sort of manual memory management. We'll leak a small amount of memory if
111 // we fail to acknowledge or drop any of these incoming invalidations.
112
113 ObjectIdSet id_set = invalidation_map.GetObjectIds();
114 for (ObjectIdSet::iterator it = id_set.begin(); it != id_set.end(); ++it) {
115 ModelType type;
116
117 // This should never happen. If it does, we'll start to leak memory.
118 if (!ObjectIdToRealModelType(*it, &type)) {
119 NOTREACHED()
120 << "Object ID " << ObjectIdToString(*it)
121 << " does not map to valid model type";
122 continue;
123 }
124
125 // Forward the invalidations to the proper recipient.
126 TypeTrackerMap::iterator tracker_it = type_trackers_.find(type);
127 DCHECK(tracker_it != type_trackers_.end());
128 tracker_it->second.RecordRemoteInvalidations(
129 invalidation_map.ForObject(*it));
130 }
131 }
132
OnInvalidationsEnabled()133 void NudgeTracker::OnInvalidationsEnabled() {
134 invalidations_enabled_ = true;
135 }
136
OnInvalidationsDisabled()137 void NudgeTracker::OnInvalidationsDisabled() {
138 invalidations_enabled_ = false;
139 invalidations_out_of_sync_ = true;
140 }
141
SetTypesThrottledUntil(ModelTypeSet types,base::TimeDelta length,base::TimeTicks now)142 void NudgeTracker::SetTypesThrottledUntil(
143 ModelTypeSet types,
144 base::TimeDelta length,
145 base::TimeTicks now) {
146 for (ModelTypeSet::Iterator it = types.First(); it.Good(); it.Inc()) {
147 TypeTrackerMap::iterator tracker_it = type_trackers_.find(it.Get());
148 tracker_it->second.ThrottleType(length, now);
149 }
150 }
151
UpdateTypeThrottlingState(base::TimeTicks now)152 void NudgeTracker::UpdateTypeThrottlingState(base::TimeTicks now) {
153 for (TypeTrackerMap::iterator it = type_trackers_.begin();
154 it != type_trackers_.end(); ++it) {
155 it->second.UpdateThrottleState(now);
156 }
157 }
158
IsAnyTypeThrottled() const159 bool NudgeTracker::IsAnyTypeThrottled() const {
160 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
161 it != type_trackers_.end(); ++it) {
162 if (it->second.IsThrottled()) {
163 return true;
164 }
165 }
166 return false;
167 }
168
IsTypeThrottled(ModelType type) const169 bool NudgeTracker::IsTypeThrottled(ModelType type) const {
170 DCHECK(type_trackers_.find(type) != type_trackers_.end());
171 return type_trackers_.find(type)->second.IsThrottled();
172 }
173
GetTimeUntilNextUnthrottle(base::TimeTicks now) const174 base::TimeDelta NudgeTracker::GetTimeUntilNextUnthrottle(
175 base::TimeTicks now) const {
176 DCHECK(IsAnyTypeThrottled()) << "This function requires a pending unthrottle";
177
178 // Return min of GetTimeUntilUnthrottle() values for all IsThrottled() types.
179 base::TimeDelta time_until_next_unthrottle = base::TimeDelta::Max();
180 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
181 it != type_trackers_.end(); ++it) {
182 if (it->second.IsThrottled()) {
183 time_until_next_unthrottle =
184 std::min(time_until_next_unthrottle,
185 it->second.GetTimeUntilUnthrottle(now));
186 }
187 }
188 DCHECK(!time_until_next_unthrottle.is_max());
189
190 return time_until_next_unthrottle;
191 }
192
GetThrottledTypes() const193 ModelTypeSet NudgeTracker::GetThrottledTypes() const {
194 ModelTypeSet result;
195 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
196 it != type_trackers_.end(); ++it) {
197 if (it->second.IsThrottled()) {
198 result.Put(it->first);
199 }
200 }
201 return result;
202 }
203
GetNudgedTypes() const204 ModelTypeSet NudgeTracker::GetNudgedTypes() const {
205 ModelTypeSet result;
206 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
207 it != type_trackers_.end(); ++it) {
208 if (it->second.HasLocalChangePending()) {
209 result.Put(it->first);
210 }
211 }
212 return result;
213 }
214
GetNotifiedTypes() const215 ModelTypeSet NudgeTracker::GetNotifiedTypes() const {
216 ModelTypeSet result;
217 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
218 it != type_trackers_.end(); ++it) {
219 if (it->second.HasPendingInvalidation()) {
220 result.Put(it->first);
221 }
222 }
223 return result;
224 }
225
GetRefreshRequestedTypes() const226 ModelTypeSet NudgeTracker::GetRefreshRequestedTypes() const {
227 ModelTypeSet result;
228 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
229 it != type_trackers_.end(); ++it) {
230 if (it->second.HasRefreshRequestPending()) {
231 result.Put(it->first);
232 }
233 }
234 return result;
235 }
236
SetLegacyNotificationHint(ModelType type,sync_pb::DataTypeProgressMarker * progress) const237 void NudgeTracker::SetLegacyNotificationHint(
238 ModelType type,
239 sync_pb::DataTypeProgressMarker* progress) const {
240 DCHECK(type_trackers_.find(type) != type_trackers_.end());
241 type_trackers_.find(type)->second.SetLegacyNotificationHint(progress);
242 }
243
GetLegacySource() const244 sync_pb::GetUpdatesCallerInfo::GetUpdatesSource NudgeTracker::GetLegacySource()
245 const {
246 // There's an order to these sources: NOTIFICATION, DATATYPE_REFRESH, LOCAL,
247 // RETRY. The server makes optimization decisions based on this field, so
248 // it's important to get this right. Setting it wrong could lead to missed
249 // updates.
250 //
251 // This complexity is part of the reason why we're deprecating 'source' in
252 // favor of 'origin'.
253 bool has_invalidation_pending = false;
254 bool has_refresh_request_pending = false;
255 bool has_commit_pending = false;
256 bool has_retry = IsRetryRequired();
257
258 for (TypeTrackerMap::const_iterator it = type_trackers_.begin();
259 it != type_trackers_.end(); ++it) {
260 const DataTypeTracker& tracker = it->second;
261 if (!tracker.IsThrottled() && tracker.HasPendingInvalidation()) {
262 has_invalidation_pending = true;
263 }
264 if (!tracker.IsThrottled() && tracker.HasRefreshRequestPending()) {
265 has_refresh_request_pending = true;
266 }
267 if (!tracker.IsThrottled() && tracker.HasLocalChangePending()) {
268 has_commit_pending = true;
269 }
270 }
271
272 if (has_invalidation_pending) {
273 return sync_pb::GetUpdatesCallerInfo::NOTIFICATION;
274 } else if (has_refresh_request_pending) {
275 return sync_pb::GetUpdatesCallerInfo::DATATYPE_REFRESH;
276 } else if (has_commit_pending) {
277 return sync_pb::GetUpdatesCallerInfo::LOCAL;
278 } else if (has_retry) {
279 return sync_pb::GetUpdatesCallerInfo::RETRY;
280 } else {
281 return sync_pb::GetUpdatesCallerInfo::UNKNOWN;
282 }
283 }
284
FillProtoMessage(ModelType type,sync_pb::GetUpdateTriggers * msg) const285 void NudgeTracker::FillProtoMessage(
286 ModelType type,
287 sync_pb::GetUpdateTriggers* msg) const {
288 DCHECK(type_trackers_.find(type) != type_trackers_.end());
289
290 // Fill what we can from the global data.
291 msg->set_invalidations_out_of_sync(invalidations_out_of_sync_);
292
293 // Delegate the type-specific work to the DataTypeTracker class.
294 type_trackers_.find(type)->second.FillGetUpdatesTriggersMessage(msg);
295 }
296
SetSyncCycleStartTime(base::TimeTicks now)297 void NudgeTracker::SetSyncCycleStartTime(base::TimeTicks now) {
298 sync_cycle_start_time_ = now;
299
300 // If current_retry_time_ is still set, that means we have an old retry time
301 // left over from a previous cycle. For example, maybe we tried to perform
302 // this retry, hit a network connection error, and now we're in exponential
303 // backoff. In that case, we want this sync cycle to include the GU retry
304 // flag so we leave this variable set regardless of whether or not there is an
305 // overwrite pending.
306 if (!current_retry_time_.is_null()) {
307 return;
308 }
309
310 // If do not have a current_retry_time_, but we do have a next_retry_time_ and
311 // it is ready to go, then we set it as the current_retry_time_. It will stay
312 // there until a GU retry has succeeded.
313 if (!next_retry_time_.is_null() &&
314 next_retry_time_ < sync_cycle_start_time_) {
315 current_retry_time_ = next_retry_time_;
316 next_retry_time_ = base::TimeTicks();
317 }
318 }
319
SetHintBufferSize(size_t size)320 void NudgeTracker::SetHintBufferSize(size_t size) {
321 for (TypeTrackerMap::iterator it = type_trackers_.begin();
322 it != type_trackers_.end(); ++it) {
323 it->second.UpdatePayloadBufferSize(size);
324 }
325 }
326
SetNextRetryTime(base::TimeTicks retry_time)327 void NudgeTracker::SetNextRetryTime(base::TimeTicks retry_time) {
328 next_retry_time_ = retry_time;
329 }
330
331 } // namespace sessions
332 } // namespace syncer
333