| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "sync/notifier/registration_manager.h" |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <iterator> |
| #include <string> |
| #include <utility> |
| |
| #include "base/rand_util.h" |
| #include "base/stl_util.h" |
| #include "google/cacheinvalidation/include/invalidation-client.h" |
| #include "google/cacheinvalidation/include/types.h" |
| #include "sync/notifier/invalidation_util.h" |
| |
| namespace syncer { |
| |
| RegistrationManager::PendingRegistrationInfo::PendingRegistrationInfo() {} |
| |
| RegistrationManager::RegistrationStatus::RegistrationStatus( |
| const invalidation::ObjectId& id, RegistrationManager* manager) |
| : id(id), |
| registration_manager(manager), |
| enabled(true), |
| state(invalidation::InvalidationListener::UNREGISTERED) { |
| DCHECK(registration_manager); |
| } |
| |
| RegistrationManager::RegistrationStatus::~RegistrationStatus() {} |
| |
| void RegistrationManager::RegistrationStatus::DoRegister() { |
| CHECK(enabled); |
| // We might be called explicitly, so stop the timer manually and |
| // reset the delay. |
| registration_timer.Stop(); |
| delay = base::TimeDelta(); |
| registration_manager->DoRegisterId(id); |
| DCHECK(!last_registration_request.is_null()); |
| } |
| |
| void RegistrationManager::RegistrationStatus::Disable() { |
| enabled = false; |
| state = invalidation::InvalidationListener::UNREGISTERED; |
| registration_timer.Stop(); |
| delay = base::TimeDelta(); |
| } |
| |
| const int RegistrationManager::kInitialRegistrationDelaySeconds = 5; |
| const int RegistrationManager::kRegistrationDelayExponent = 2; |
| const double RegistrationManager::kRegistrationDelayMaxJitter = 0.5; |
| const int RegistrationManager::kMinRegistrationDelaySeconds = 1; |
| // 1 hour. |
| const int RegistrationManager::kMaxRegistrationDelaySeconds = 60 * 60; |
| |
| RegistrationManager::RegistrationManager( |
| invalidation::InvalidationClient* invalidation_client) |
| : invalidation_client_(invalidation_client) { |
| DCHECK(invalidation_client_); |
| } |
| |
| RegistrationManager::~RegistrationManager() { |
| DCHECK(CalledOnValidThread()); |
| STLDeleteValues(®istration_statuses_); |
| } |
| |
| ObjectIdSet RegistrationManager::UpdateRegisteredIds(const ObjectIdSet& ids) { |
| DCHECK(CalledOnValidThread()); |
| |
| const ObjectIdSet& old_ids = GetRegisteredIds(); |
| const ObjectIdSet& to_register = ids; |
| ObjectIdSet to_unregister; |
| std::set_difference(old_ids.begin(), old_ids.end(), |
| ids.begin(), ids.end(), |
| std::inserter(to_unregister, to_unregister.begin()), |
| ObjectIdLessThan()); |
| |
| for (ObjectIdSet::const_iterator it = to_unregister.begin(); |
| it != to_unregister.end(); ++it) { |
| UnregisterId(*it); |
| } |
| |
| for (ObjectIdSet::const_iterator it = to_register.begin(); |
| it != to_register.end(); ++it) { |
| if (!ContainsKey(registration_statuses_, *it)) { |
| registration_statuses_.insert( |
| std::make_pair(*it, new RegistrationStatus(*it, this))); |
| } |
| if (!IsIdRegistered(*it)) { |
| TryRegisterId(*it, false /* is-retry */); |
| } |
| } |
| |
| return to_unregister; |
| } |
| |
| void RegistrationManager::MarkRegistrationLost( |
| const invalidation::ObjectId& id) { |
| DCHECK(CalledOnValidThread()); |
| RegistrationStatusMap::const_iterator it = registration_statuses_.find(id); |
| if (it == registration_statuses_.end()) { |
| DLOG(WARNING) << "Attempt to mark non-existent registration for " |
| << ObjectIdToString(id) << " as lost"; |
| return; |
| } |
| if (!it->second->enabled) { |
| return; |
| } |
| it->second->state = invalidation::InvalidationListener::UNREGISTERED; |
| bool is_retry = !it->second->last_registration_request.is_null(); |
| TryRegisterId(id, is_retry); |
| } |
| |
| void RegistrationManager::MarkAllRegistrationsLost() { |
| DCHECK(CalledOnValidThread()); |
| for (RegistrationStatusMap::const_iterator it = |
| registration_statuses_.begin(); |
| it != registration_statuses_.end(); ++it) { |
| if (IsIdRegistered(it->first)) { |
| MarkRegistrationLost(it->first); |
| } |
| } |
| } |
| |
| void RegistrationManager::DisableId(const invalidation::ObjectId& id) { |
| DCHECK(CalledOnValidThread()); |
| RegistrationStatusMap::const_iterator it = registration_statuses_.find(id); |
| if (it == registration_statuses_.end()) { |
| DLOG(WARNING) << "Attempt to disable non-existent registration for " |
| << ObjectIdToString(id); |
| return; |
| } |
| it->second->Disable(); |
| } |
| |
| // static |
| double RegistrationManager::CalculateBackoff( |
| double retry_interval, |
| double initial_retry_interval, |
| double min_retry_interval, |
| double max_retry_interval, |
| double backoff_exponent, |
| double jitter, |
| double max_jitter) { |
| // scaled_jitter lies in [-max_jitter, max_jitter]. |
| double scaled_jitter = jitter * max_jitter; |
| double new_retry_interval = |
| (retry_interval == 0.0) ? |
| (initial_retry_interval * (1.0 + scaled_jitter)) : |
| (retry_interval * (backoff_exponent + scaled_jitter)); |
| return std::max(min_retry_interval, |
| std::min(max_retry_interval, new_retry_interval)); |
| } |
| |
| ObjectIdSet RegistrationManager::GetRegisteredIdsForTest() const { |
| return GetRegisteredIds(); |
| } |
| |
| RegistrationManager::PendingRegistrationMap |
| RegistrationManager::GetPendingRegistrationsForTest() const { |
| DCHECK(CalledOnValidThread()); |
| PendingRegistrationMap pending_registrations; |
| for (RegistrationStatusMap::const_iterator it = |
| registration_statuses_.begin(); |
| it != registration_statuses_.end(); ++it) { |
| const invalidation::ObjectId& id = it->first; |
| RegistrationStatus* status = it->second; |
| if (status->registration_timer.IsRunning()) { |
| pending_registrations[id].last_registration_request = |
| status->last_registration_request; |
| pending_registrations[id].registration_attempt = |
| status->last_registration_attempt; |
| pending_registrations[id].delay = status->delay; |
| pending_registrations[id].actual_delay = |
| status->registration_timer.GetCurrentDelay(); |
| } |
| } |
| return pending_registrations; |
| } |
| |
| void RegistrationManager::FirePendingRegistrationsForTest() { |
| DCHECK(CalledOnValidThread()); |
| for (RegistrationStatusMap::const_iterator it = |
| registration_statuses_.begin(); |
| it != registration_statuses_.end(); ++it) { |
| if (it->second->registration_timer.IsRunning()) { |
| it->second->DoRegister(); |
| } |
| } |
| } |
| |
| double RegistrationManager::GetJitter() { |
| // |jitter| lies in [-1.0, 1.0), which is low-biased, but only |
| // barely. |
| // |
| // TODO(akalin): Fix the bias. |
| return 2.0 * base::RandDouble() - 1.0; |
| } |
| |
| void RegistrationManager::TryRegisterId(const invalidation::ObjectId& id, |
| bool is_retry) { |
| DCHECK(CalledOnValidThread()); |
| RegistrationStatusMap::const_iterator it = registration_statuses_.find(id); |
| if (it == registration_statuses_.end()) { |
| DLOG(FATAL) << "TryRegisterId called on " << ObjectIdToString(id) |
| << " which is not in the registration map"; |
| return; |
| } |
| RegistrationStatus* status = it->second; |
| if (!status->enabled) { |
| // Disabled, so do nothing. |
| return; |
| } |
| status->last_registration_attempt = base::Time::Now(); |
| if (is_retry) { |
| // If we're a retry, we must have tried at least once before. |
| DCHECK(!status->last_registration_request.is_null()); |
| // delay = max(0, (now - last request) + next_delay) |
| status->delay = |
| (status->last_registration_request - |
| status->last_registration_attempt) + |
| status->next_delay; |
| base::TimeDelta delay = |
| (status->delay <= base::TimeDelta()) ? |
| base::TimeDelta() : status->delay; |
| DVLOG(2) << "Registering " |
| << ObjectIdToString(id) << " in " |
| << delay.InMilliseconds() << " ms"; |
| status->registration_timer.Stop(); |
| status->registration_timer.Start(FROM_HERE, |
| delay, status, &RegistrationManager::RegistrationStatus::DoRegister); |
| double next_delay_seconds = |
| CalculateBackoff(static_cast<double>(status->next_delay.InSeconds()), |
| kInitialRegistrationDelaySeconds, |
| kMinRegistrationDelaySeconds, |
| kMaxRegistrationDelaySeconds, |
| kRegistrationDelayExponent, |
| GetJitter(), |
| kRegistrationDelayMaxJitter); |
| status->next_delay = |
| base::TimeDelta::FromSeconds(static_cast<int64>(next_delay_seconds)); |
| DVLOG(2) << "New next delay for " |
| << ObjectIdToString(id) << " is " |
| << status->next_delay.InSeconds() << " seconds"; |
| } else { |
| DVLOG(2) << "Not a retry -- registering " |
| << ObjectIdToString(id) << " immediately"; |
| status->delay = base::TimeDelta(); |
| status->next_delay = base::TimeDelta(); |
| status->DoRegister(); |
| } |
| } |
| |
| void RegistrationManager::DoRegisterId(const invalidation::ObjectId& id) { |
| DCHECK(CalledOnValidThread()); |
| invalidation_client_->Register(id); |
| RegistrationStatusMap::const_iterator it = registration_statuses_.find(id); |
| if (it == registration_statuses_.end()) { |
| DLOG(FATAL) << "DoRegisterId called on " << ObjectIdToString(id) |
| << " which is not in the registration map"; |
| return; |
| } |
| it->second->state = invalidation::InvalidationListener::REGISTERED; |
| it->second->last_registration_request = base::Time::Now(); |
| } |
| |
| void RegistrationManager::UnregisterId(const invalidation::ObjectId& id) { |
| DCHECK(CalledOnValidThread()); |
| invalidation_client_->Unregister(id); |
| RegistrationStatusMap::iterator it = registration_statuses_.find(id); |
| if (it == registration_statuses_.end()) { |
| DLOG(FATAL) << "UnregisterId called on " << ObjectIdToString(id) |
| << " which is not in the registration map"; |
| return; |
| } |
| delete it->second; |
| registration_statuses_.erase(it); |
| } |
| |
| |
| ObjectIdSet RegistrationManager::GetRegisteredIds() const { |
| DCHECK(CalledOnValidThread()); |
| ObjectIdSet ids; |
| for (RegistrationStatusMap::const_iterator it = |
| registration_statuses_.begin(); |
| it != registration_statuses_.end(); ++it) { |
| if (IsIdRegistered(it->first)) { |
| ids.insert(it->first); |
| } |
| } |
| return ids; |
| } |
| |
| bool RegistrationManager::IsIdRegistered( |
| const invalidation::ObjectId& id) const { |
| DCHECK(CalledOnValidThread()); |
| RegistrationStatusMap::const_iterator it = |
| registration_statuses_.find(id); |
| return it != registration_statuses_.end() && |
| it->second->state == invalidation::InvalidationListener::REGISTERED; |
| } |
| |
| } // namespace syncer |