// 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