// Copyright (c) 2010 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 "chrome/browser/sync/notifier/registration_manager.h" #include <algorithm> #include <cstddef> #include <string> #include "base/rand_util.h" #include "chrome/browser/sync/notifier/invalidation_util.h" #include "chrome/browser/sync/syncable/model_type.h" namespace sync_notifier { RegistrationManager::PendingRegistrationInfo::PendingRegistrationInfo() {} RegistrationManager::RegistrationStatus::RegistrationStatus() : model_type(syncable::UNSPECIFIED), registration_manager(NULL), state(invalidation::RegistrationState_UNREGISTERED) {} RegistrationManager::RegistrationStatus::~RegistrationStatus() {} void RegistrationManager::RegistrationStatus::DoRegister() { DCHECK_NE(model_type, syncable::UNSPECIFIED); DCHECK(registration_manager); // We might be called explicitly, so stop the timer manually and // reset the delay. registration_timer.Stop(); delay = base::TimeDelta(); registration_manager->DoRegisterType(model_type); DCHECK(!last_registration_request.is_null()); } 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_); // Initialize statuses. for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); RegistrationStatus* status = ®istration_statuses_[model_type]; status->model_type = model_type; status->registration_manager = this; } } RegistrationManager::~RegistrationManager() { DCHECK(non_thread_safe_.CalledOnValidThread()); } void RegistrationManager::SetRegisteredTypes( const syncable::ModelTypeSet& types) { DCHECK(non_thread_safe_.CalledOnValidThread()); for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); if (types.count(model_type) > 0) { if (!IsTypeRegistered(model_type)) { TryRegisterType(model_type, false /* is_retry */); } } else { if (IsTypeRegistered(model_type)) { UnregisterType(model_type); } } } } void RegistrationManager::MarkRegistrationLost( syncable::ModelType model_type) { DCHECK(non_thread_safe_.CalledOnValidThread()); registration_statuses_[model_type].state = invalidation::RegistrationState_UNREGISTERED; TryRegisterType(model_type, true /* is_retry */); } void RegistrationManager::MarkAllRegistrationsLost() { DCHECK(non_thread_safe_.CalledOnValidThread()); for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); if (IsTypeRegistered(model_type)) { MarkRegistrationLost(model_type); } } } syncable::ModelTypeSet RegistrationManager::GetRegisteredTypes() const { DCHECK(non_thread_safe_.CalledOnValidThread()); syncable::ModelTypeSet registered_types; for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); if (IsTypeRegistered(model_type)) { registered_types.insert(model_type); } } return registered_types; } RegistrationManager::PendingRegistrationMap RegistrationManager::GetPendingRegistrations() const { DCHECK(non_thread_safe_.CalledOnValidThread()); PendingRegistrationMap pending_registrations; for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); const RegistrationStatus& status = registration_statuses_[model_type]; if (status.registration_timer.IsRunning()) { pending_registrations[model_type].last_registration_request = status.last_registration_request; pending_registrations[model_type].registration_attempt = status.last_registration_attempt; pending_registrations[model_type].delay = status.delay; pending_registrations[model_type].actual_delay = status.registration_timer.GetCurrentDelay(); } } return pending_registrations; } void RegistrationManager::FirePendingRegistrationsForTest() { DCHECK(non_thread_safe_.CalledOnValidThread()); for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i); RegistrationStatus* status = ®istration_statuses_[model_type]; if (status->registration_timer.IsRunning()) { status->DoRegister(); } } } // 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)); } 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::TryRegisterType(syncable::ModelType model_type, bool is_retry) { DCHECK(non_thread_safe_.CalledOnValidThread()); RegistrationStatus* status = ®istration_statuses_[model_type]; 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; VLOG(2) << "Registering " << syncable::ModelTypeToString(model_type) << " in " << delay.InMilliseconds() << " ms"; status->registration_timer.Stop(); status->registration_timer.Start( 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)); VLOG(2) << "New next delay for " << syncable::ModelTypeToString(model_type) << " is " << status->next_delay.InSeconds() << " seconds"; } else { VLOG(2) << "Not a retry -- registering " << syncable::ModelTypeToString(model_type) << " immediately"; status->delay = base::TimeDelta(); status->next_delay = base::TimeDelta(); status->DoRegister(); } } void RegistrationManager::DoRegisterType(syncable::ModelType model_type) { DCHECK(non_thread_safe_.CalledOnValidThread()); invalidation::ObjectId object_id; if (!RealModelTypeToObjectId(model_type, &object_id)) { LOG(DFATAL) << "Invalid model type: " << model_type; return; } invalidation_client_->Register(object_id); RegistrationStatus* status = ®istration_statuses_[model_type]; status->state = invalidation::RegistrationState_REGISTERED; status->last_registration_request = base::Time::Now(); } void RegistrationManager::UnregisterType(syncable::ModelType model_type) { DCHECK(non_thread_safe_.CalledOnValidThread()); invalidation::ObjectId object_id; if (!RealModelTypeToObjectId(model_type, &object_id)) { LOG(DFATAL) << "Invalid model type: " << model_type; return; } invalidation_client_->Unregister(object_id); RegistrationStatus* status = ®istration_statuses_[model_type]; status->state = invalidation::RegistrationState_UNREGISTERED; } bool RegistrationManager::IsTypeRegistered( syncable::ModelType model_type) const { DCHECK(non_thread_safe_.CalledOnValidThread()); return registration_statuses_[model_type].state == invalidation::RegistrationState_REGISTERED; } } // namespace sync_notifier