1 // Copyright (c) 2012 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/notifier/registration_manager.h"
6
7 #include <algorithm>
8 #include <cstddef>
9 #include <iterator>
10 #include <string>
11 #include <utility>
12
13 #include "base/rand_util.h"
14 #include "base/stl_util.h"
15 #include "google/cacheinvalidation/include/invalidation-client.h"
16 #include "google/cacheinvalidation/include/types.h"
17 #include "sync/notifier/invalidation_util.h"
18
19 namespace syncer {
20
PendingRegistrationInfo()21 RegistrationManager::PendingRegistrationInfo::PendingRegistrationInfo() {}
22
RegistrationStatus(const invalidation::ObjectId & id,RegistrationManager * manager)23 RegistrationManager::RegistrationStatus::RegistrationStatus(
24 const invalidation::ObjectId& id, RegistrationManager* manager)
25 : id(id),
26 registration_manager(manager),
27 enabled(true),
28 state(invalidation::InvalidationListener::UNREGISTERED) {
29 DCHECK(registration_manager);
30 }
31
~RegistrationStatus()32 RegistrationManager::RegistrationStatus::~RegistrationStatus() {}
33
DoRegister()34 void RegistrationManager::RegistrationStatus::DoRegister() {
35 CHECK(enabled);
36 // We might be called explicitly, so stop the timer manually and
37 // reset the delay.
38 registration_timer.Stop();
39 delay = base::TimeDelta();
40 registration_manager->DoRegisterId(id);
41 DCHECK(!last_registration_request.is_null());
42 }
43
Disable()44 void RegistrationManager::RegistrationStatus::Disable() {
45 enabled = false;
46 state = invalidation::InvalidationListener::UNREGISTERED;
47 registration_timer.Stop();
48 delay = base::TimeDelta();
49 }
50
51 const int RegistrationManager::kInitialRegistrationDelaySeconds = 5;
52 const int RegistrationManager::kRegistrationDelayExponent = 2;
53 const double RegistrationManager::kRegistrationDelayMaxJitter = 0.5;
54 const int RegistrationManager::kMinRegistrationDelaySeconds = 1;
55 // 1 hour.
56 const int RegistrationManager::kMaxRegistrationDelaySeconds = 60 * 60;
57
RegistrationManager(invalidation::InvalidationClient * invalidation_client)58 RegistrationManager::RegistrationManager(
59 invalidation::InvalidationClient* invalidation_client)
60 : invalidation_client_(invalidation_client) {
61 DCHECK(invalidation_client_);
62 }
63
~RegistrationManager()64 RegistrationManager::~RegistrationManager() {
65 DCHECK(CalledOnValidThread());
66 STLDeleteValues(®istration_statuses_);
67 }
68
UpdateRegisteredIds(const ObjectIdSet & ids)69 ObjectIdSet RegistrationManager::UpdateRegisteredIds(const ObjectIdSet& ids) {
70 DCHECK(CalledOnValidThread());
71
72 const ObjectIdSet& old_ids = GetRegisteredIds();
73 const ObjectIdSet& to_register = ids;
74 ObjectIdSet to_unregister;
75 std::set_difference(old_ids.begin(), old_ids.end(),
76 ids.begin(), ids.end(),
77 std::inserter(to_unregister, to_unregister.begin()),
78 ObjectIdLessThan());
79
80 for (ObjectIdSet::const_iterator it = to_unregister.begin();
81 it != to_unregister.end(); ++it) {
82 UnregisterId(*it);
83 }
84
85 for (ObjectIdSet::const_iterator it = to_register.begin();
86 it != to_register.end(); ++it) {
87 if (!ContainsKey(registration_statuses_, *it)) {
88 registration_statuses_.insert(
89 std::make_pair(*it, new RegistrationStatus(*it, this)));
90 }
91 if (!IsIdRegistered(*it)) {
92 TryRegisterId(*it, false /* is-retry */);
93 }
94 }
95
96 return to_unregister;
97 }
98
MarkRegistrationLost(const invalidation::ObjectId & id)99 void RegistrationManager::MarkRegistrationLost(
100 const invalidation::ObjectId& id) {
101 DCHECK(CalledOnValidThread());
102 RegistrationStatusMap::const_iterator it = registration_statuses_.find(id);
103 if (it == registration_statuses_.end()) {
104 DVLOG(1) << "Attempt to mark non-existent registration for "
105 << ObjectIdToString(id) << " as lost";
106 return;
107 }
108 if (!it->second->enabled) {
109 return;
110 }
111 it->second->state = invalidation::InvalidationListener::UNREGISTERED;
112 bool is_retry = !it->second->last_registration_request.is_null();
113 TryRegisterId(id, is_retry);
114 }
115
MarkAllRegistrationsLost()116 void RegistrationManager::MarkAllRegistrationsLost() {
117 DCHECK(CalledOnValidThread());
118 for (RegistrationStatusMap::const_iterator it =
119 registration_statuses_.begin();
120 it != registration_statuses_.end(); ++it) {
121 if (IsIdRegistered(it->first)) {
122 MarkRegistrationLost(it->first);
123 }
124 }
125 }
126
DisableId(const invalidation::ObjectId & id)127 void RegistrationManager::DisableId(const invalidation::ObjectId& id) {
128 DCHECK(CalledOnValidThread());
129 RegistrationStatusMap::const_iterator it = registration_statuses_.find(id);
130 if (it == registration_statuses_.end()) {
131 DVLOG(1) << "Attempt to disable non-existent registration for "
132 << ObjectIdToString(id);
133 return;
134 }
135 it->second->Disable();
136 }
137
138 // static
CalculateBackoff(double retry_interval,double initial_retry_interval,double min_retry_interval,double max_retry_interval,double backoff_exponent,double jitter,double max_jitter)139 double RegistrationManager::CalculateBackoff(
140 double retry_interval,
141 double initial_retry_interval,
142 double min_retry_interval,
143 double max_retry_interval,
144 double backoff_exponent,
145 double jitter,
146 double max_jitter) {
147 // scaled_jitter lies in [-max_jitter, max_jitter].
148 double scaled_jitter = jitter * max_jitter;
149 double new_retry_interval =
150 (retry_interval == 0.0) ?
151 (initial_retry_interval * (1.0 + scaled_jitter)) :
152 (retry_interval * (backoff_exponent + scaled_jitter));
153 return std::max(min_retry_interval,
154 std::min(max_retry_interval, new_retry_interval));
155 }
156
GetRegisteredIdsForTest() const157 ObjectIdSet RegistrationManager::GetRegisteredIdsForTest() const {
158 return GetRegisteredIds();
159 }
160
161 RegistrationManager::PendingRegistrationMap
GetPendingRegistrationsForTest() const162 RegistrationManager::GetPendingRegistrationsForTest() const {
163 DCHECK(CalledOnValidThread());
164 PendingRegistrationMap pending_registrations;
165 for (RegistrationStatusMap::const_iterator it =
166 registration_statuses_.begin();
167 it != registration_statuses_.end(); ++it) {
168 const invalidation::ObjectId& id = it->first;
169 RegistrationStatus* status = it->second;
170 if (status->registration_timer.IsRunning()) {
171 pending_registrations[id].last_registration_request =
172 status->last_registration_request;
173 pending_registrations[id].registration_attempt =
174 status->last_registration_attempt;
175 pending_registrations[id].delay = status->delay;
176 pending_registrations[id].actual_delay =
177 status->registration_timer.GetCurrentDelay();
178 }
179 }
180 return pending_registrations;
181 }
182
FirePendingRegistrationsForTest()183 void RegistrationManager::FirePendingRegistrationsForTest() {
184 DCHECK(CalledOnValidThread());
185 for (RegistrationStatusMap::const_iterator it =
186 registration_statuses_.begin();
187 it != registration_statuses_.end(); ++it) {
188 if (it->second->registration_timer.IsRunning()) {
189 it->second->DoRegister();
190 }
191 }
192 }
193
GetJitter()194 double RegistrationManager::GetJitter() {
195 // |jitter| lies in [-1.0, 1.0), which is low-biased, but only
196 // barely.
197 //
198 // TODO(akalin): Fix the bias.
199 return 2.0 * base::RandDouble() - 1.0;
200 }
201
TryRegisterId(const invalidation::ObjectId & id,bool is_retry)202 void RegistrationManager::TryRegisterId(const invalidation::ObjectId& id,
203 bool is_retry) {
204 DCHECK(CalledOnValidThread());
205 RegistrationStatusMap::const_iterator it = registration_statuses_.find(id);
206 if (it == registration_statuses_.end()) {
207 NOTREACHED() << "TryRegisterId called on " << ObjectIdToString(id)
208 << " which is not in the registration map";
209 return;
210 }
211 RegistrationStatus* status = it->second;
212 if (!status->enabled) {
213 // Disabled, so do nothing.
214 return;
215 }
216 status->last_registration_attempt = base::Time::Now();
217 if (is_retry) {
218 // If we're a retry, we must have tried at least once before.
219 DCHECK(!status->last_registration_request.is_null());
220 // delay = max(0, (now - last request) + next_delay)
221 status->delay =
222 (status->last_registration_request -
223 status->last_registration_attempt) +
224 status->next_delay;
225 base::TimeDelta delay =
226 (status->delay <= base::TimeDelta()) ?
227 base::TimeDelta() : status->delay;
228 DVLOG(2) << "Registering "
229 << ObjectIdToString(id) << " in "
230 << delay.InMilliseconds() << " ms";
231 status->registration_timer.Stop();
232 status->registration_timer.Start(FROM_HERE,
233 delay, status, &RegistrationManager::RegistrationStatus::DoRegister);
234 double next_delay_seconds =
235 CalculateBackoff(static_cast<double>(status->next_delay.InSeconds()),
236 kInitialRegistrationDelaySeconds,
237 kMinRegistrationDelaySeconds,
238 kMaxRegistrationDelaySeconds,
239 kRegistrationDelayExponent,
240 GetJitter(),
241 kRegistrationDelayMaxJitter);
242 status->next_delay =
243 base::TimeDelta::FromSeconds(static_cast<int64>(next_delay_seconds));
244 DVLOG(2) << "New next delay for "
245 << ObjectIdToString(id) << " is "
246 << status->next_delay.InSeconds() << " seconds";
247 } else {
248 DVLOG(2) << "Not a retry -- registering "
249 << ObjectIdToString(id) << " immediately";
250 status->delay = base::TimeDelta();
251 status->next_delay = base::TimeDelta();
252 status->DoRegister();
253 }
254 }
255
DoRegisterId(const invalidation::ObjectId & id)256 void RegistrationManager::DoRegisterId(const invalidation::ObjectId& id) {
257 DCHECK(CalledOnValidThread());
258 invalidation_client_->Register(id);
259 RegistrationStatusMap::const_iterator it = registration_statuses_.find(id);
260 if (it == registration_statuses_.end()) {
261 NOTREACHED() << "DoRegisterId called on " << ObjectIdToString(id)
262 << " which is not in the registration map";
263 return;
264 }
265 it->second->state = invalidation::InvalidationListener::REGISTERED;
266 it->second->last_registration_request = base::Time::Now();
267 }
268
UnregisterId(const invalidation::ObjectId & id)269 void RegistrationManager::UnregisterId(const invalidation::ObjectId& id) {
270 DCHECK(CalledOnValidThread());
271 invalidation_client_->Unregister(id);
272 RegistrationStatusMap::iterator it = registration_statuses_.find(id);
273 if (it == registration_statuses_.end()) {
274 NOTREACHED() << "UnregisterId called on " << ObjectIdToString(id)
275 << " which is not in the registration map";
276 return;
277 }
278 delete it->second;
279 registration_statuses_.erase(it);
280 }
281
282
GetRegisteredIds() const283 ObjectIdSet RegistrationManager::GetRegisteredIds() const {
284 DCHECK(CalledOnValidThread());
285 ObjectIdSet ids;
286 for (RegistrationStatusMap::const_iterator it =
287 registration_statuses_.begin();
288 it != registration_statuses_.end(); ++it) {
289 if (IsIdRegistered(it->first)) {
290 ids.insert(it->first);
291 }
292 }
293 return ids;
294 }
295
IsIdRegistered(const invalidation::ObjectId & id) const296 bool RegistrationManager::IsIdRegistered(
297 const invalidation::ObjectId& id) const {
298 DCHECK(CalledOnValidThread());
299 RegistrationStatusMap::const_iterator it =
300 registration_statuses_.find(id);
301 return it != registration_statuses_.end() &&
302 it->second->state == invalidation::InvalidationListener::REGISTERED;
303 }
304
305 } // namespace syncer
306