// 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()) { DVLOG(1) << "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()) { DVLOG(1) << "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()) { NOTREACHED() << "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()) { NOTREACHED() << "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()) { NOTREACHED() << "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