| // Copyright 2014 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 "components/invalidation/impl/registration_manager.h" | 
 |  | 
 | #include <stdint.h> | 
 |  | 
 | #include <algorithm> | 
 | #include <cstddef> | 
 | #include <iterator> | 
 | #include <memory> | 
 | #include <string> | 
 | #include <utility> | 
 |  | 
 | #include "base/rand_util.h" | 
 | #include "base/stl_util.h" | 
 | #include "components/invalidation/public/invalidation_util.h" | 
 | #include "google/cacheinvalidation/include/invalidation-client.h" | 
 | #include "google/cacheinvalidation/include/types.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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 | } | 
 |  | 
 | ObjectIdSet RegistrationManager::UpdateRegisteredIds(const ObjectIdSet& ids) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |  | 
 |   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 (!base::ContainsKey(registration_statuses_, *it)) { | 
 |       registration_statuses_[*it] = | 
 |           std::make_unique<RegistrationStatus>(*it, this); | 
 |     } | 
 |     if (!IsIdRegistered(*it)) { | 
 |       TryRegisterId(*it, false /* is-retry */); | 
 |     } | 
 |   } | 
 |  | 
 |   return to_unregister; | 
 | } | 
 |  | 
 | void RegistrationManager::MarkRegistrationLost( | 
 |     const invalidation::ObjectId& id) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   auto 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   for (auto it = registration_statuses_.begin(); | 
 |        it != registration_statuses_.end(); ++it) { | 
 |     if (IsIdRegistered(it->first)) { | 
 |       MarkRegistrationLost(it->first); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void RegistrationManager::DisableId(const invalidation::ObjectId& id) { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   auto 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   PendingRegistrationMap pending_registrations; | 
 |   for (const auto& status_pair : registration_statuses_) { | 
 |     const invalidation::ObjectId& id = status_pair.first; | 
 |     RegistrationStatus* status = status_pair.second.get(); | 
 |     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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   for (const auto& status_pair : registration_statuses_) { | 
 |     if (status_pair.second->registration_timer.IsRunning()) { | 
 |       status_pair.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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   auto 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.get(); | 
 |   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_t>(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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   invalidation_client_->Register(id); | 
 |   auto 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_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   invalidation_client_->Unregister(id); | 
 |   auto it = registration_statuses_.find(id); | 
 |   if (it == registration_statuses_.end()) { | 
 |     NOTREACHED() << "UnregisterId called on " << ObjectIdToString(id) | 
 |                  << " which is not in the registration map"; | 
 |     return; | 
 |   } | 
 |   registration_statuses_.erase(it); | 
 | } | 
 |  | 
 |  | 
 | ObjectIdSet RegistrationManager::GetRegisteredIds() const { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   ObjectIdSet ids; | 
 |   for (const auto& status_pair : registration_statuses_) { | 
 |     if (IsIdRegistered(status_pair.first)) { | 
 |       ids.insert(status_pair.first); | 
 |     } | 
 |   } | 
 |   return ids; | 
 | } | 
 |  | 
 | bool RegistrationManager::IsIdRegistered( | 
 |     const invalidation::ObjectId& id) const { | 
 |   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); | 
 |   auto it = registration_statuses_.find(id); | 
 |   return it != registration_statuses_.end() && | 
 |       it->second->state == invalidation::InvalidationListener::REGISTERED; | 
 | } | 
 |  | 
 | }  // namespace syncer |