blob: 4150c905ef306dd0a283c0c0eb2cae3f640a0e38 [file]
// Copyright 2010 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "google/cacheinvalidation/invalidation-client-impl.h"
#include <sstream>
#include <string>
#include "google/cacheinvalidation/log-macro.h"
#include "google/cacheinvalidation/persistence-utils.h"
#include "google/cacheinvalidation/proto-converter.h"
#include "google/cacheinvalidation/stl-namespace.h"
namespace invalidation {
namespace {
using INVALIDATION_STL_NAMESPACE::hex;
using INVALIDATION_STL_NAMESPACE::ostringstream;
// Used by HandleNewSession().
string EscapeChars(const string& str) {
ostringstream oss;
for (string::size_type i = 0; i < str.size(); ++i) {
int x = str[i];
oss << "\\x" << hex << x;
}
return oss.str();
}
} // namespace
using INVALIDATION_STL_NAMESPACE::string;
const char* InvalidationClientImpl::INVALIDATE_ALL_OBJECT_NAME = "ALL";
// Runs a closure in its destructor. By declaring it near the beginning of a
// function, we ensure that the closure will run at exit from the function,
// regardless of how we exit the function.
class Finally {
public:
explicit Finally(Closure* task) : task_(task) {
CHECK(IsCallbackRepeatable(task));
}
~Finally() {
task_->Run();
delete task_;
}
private:
Closure* task_;
};
InvalidationClientImpl::InvalidationClientImpl(
SystemResources* resources,
const ClientType& client_type,
const string& app_name,
const string& client_info,
const ClientConfig& config,
InvalidationListener* listener)
: resources_(resources),
client_type_(client_type),
app_name_(app_name),
listener_(listener),
config_(config),
network_manager_(ALLOW_THIS_IN_INITIALIZER_LIST(this),
resources, client_info, config),
persistence_manager_(resources_),
awaiting_seqno_writeback_(false),
is_started_(false),
random_(resources->current_time().ToInternalValue()) {
}
void InvalidationClientImpl::Start(const string& serialized_state) {
CHECK(!is_started_) << "client already started";
// Initialize the registration and session managers from persisted state if
// present.
TiclState persistent_state;
uint64 initial_seqno;
string uniquifier;
string session_token;
bool is_restart = DeserializeState(serialized_state, &persistent_state);
if (!is_restart && !serialized_state.empty()) {
// The persisted state appears to be corrupt.
TLOG(SEVERE_LEVEL, "Got persisted state but failed to deserialize");
}
if (is_restart) {
// The Ticl is being restarted with a uniquifier, session token, and
// sequence number from persistent storage. In this case, we initialize a
// session manager with these persisted values, and we start the
// registration manager with the persisted sequence number. Before we can
// send out any registrations, we need to write back a new state blob
// reserving a new block of sequence numbers. If that fails, then we need
// to forget the persisted client id and session and do a fresh start.
uniquifier = persistent_state.uniquifier();
session_token = persistent_state.session_token();
initial_seqno = persistent_state.sequence_number_limit() + 1;
} else {
// Either we had no persisted state, or we couldn't parse it, so we'll start
// fresh. When we get a session, we'll attempt to write out our state. In
// the case of a non-persistent client, the write will appear to succeed.
uniquifier = "";
session_token = "";
initial_seqno = RegistrationUpdateManager::kFirstSequenceNumber;
}
session_manager_.reset(
new SessionManager(config_, client_type_, app_name_, resources_,
uniquifier, session_token));
registration_manager_.reset(
new RegistrationUpdateManager(resources_, config_, initial_seqno,
listener_));
if (is_restart) {
// If we started from persisted state, then we "have" a session already, and
// we need to write back a state blob to claim a new block of sequence
// numbers.
TLOG(INFO_LEVEL, "Taking session actions for persistent state restart");
registration_manager_->HandleNewSession();
resources_->ScheduleOnListenerThread(
NewPermanentCallback(
listener_,
&InvalidationListener::SessionStatusChanged,
true));
AllocateNewSequenceNumbers(persistent_state);
} else {
// If we're starting fresh, then we can claim an initial block of sequence
// numbers without writing out state. When we get a session, we'll attempt
// to update the state with the session token, etc.
TLOG(INFO_LEVEL, "Taking actions for fresh start");
registration_manager_->UpdateMaximumSeqno(config_.seqno_block_size);
}
resources_->ScheduleImmediately(
NewPermanentCallback(this, &InvalidationClientImpl::PeriodicTask));
is_started_ = true;
}
void InvalidationClientImpl::AllocateNewSequenceNumbers(
const TiclState& persistent_state) {
// The new maximum op sequence number is equal to the previously persisted
// maximum plus the block size.
int64 maximum_op_seqno_inclusive =
persistent_state.sequence_number_limit() + config_.seqno_block_size;
// We've just restarted from persisted state, so we're going to reuse the old
// session token and uniquifier. The only thing that changes is the sequence
// number.
TiclState new_state;
new_state.CopyFrom(persistent_state);
new_state.set_sequence_number_limit(maximum_op_seqno_inclusive);
awaiting_seqno_writeback_ = true;
string serialized;
SerializeState(new_state, &serialized);
persistence_manager_.WriteState(
serialized,
NewPermanentCallback(
this,
&InvalidationClientImpl::HandleSeqnoWritebackResult,
maximum_op_seqno_inclusive));
}
void InvalidationClientImpl::HandleSeqnoWritebackResult(
int64 maximum_op_seqno_inclusive, bool success) {
MutexLock m(&lock_);
TLOG(INFO_LEVEL, "seqno writeback returned %d", success);
awaiting_seqno_writeback_ = false;
if (success) {
registration_manager_->UpdateMaximumSeqno(maximum_op_seqno_inclusive);
} else {
// We couldn't reserve a new block of sequence numbers, so we'll forget our
// old client id and start fresh, rather than getting blocked indefinitely
// retrying the write.
ForgetClientId();
}
}
void InvalidationClientImpl::HandleBestEffortWrite(bool result) {
TLOG(INFO_LEVEL, "Write completed with result: %d", result);
}
void InvalidationClientImpl::PeriodicTask() {
MutexLock m(&lock_);
// Compute a random delay that's approximately the periodic task interval.
TimeDelta smeared_delay = SmearDelay(
config_.periodic_task_interval, config_.smear_factor, &random_);
// When we exit this function, reschedule the periodic task to run after the
// randomized delay. This way the time spent executing this function won't
// shorten the delay between when it executes.
Finally reschedule_periodic_task(
NewPermanentCallback(
resources_,
&SystemResources::ScheduleWithDelay,
smeared_delay,
NewPermanentCallback(this, &InvalidationClientImpl::PeriodicTask)));
persistence_manager_.DoPeriodicCheck();
if (awaiting_seqno_writeback_) {
TLOG(INFO_LEVEL, "Skipping periodic check while awaiting local write");
// Don't send any messages until the initial write-back has finished.
return;
}
// Check if we have run out of sequence numbers. If so, restart as a new
// client.
if (registration_manager_->current_op_seqno() >
registration_manager_->maximum_op_seqno_inclusive()) {
TLOG(INFO_LEVEL, "Exhausted seqnos; forgetting client id");
ForgetClientId();
}
// Check for session data to send.
bool have_session_data = session_manager_->HasDataToSend();
// Check for registrations to send.
bool have_registration_data =
registration_manager_->DoPeriodicRegistrationCheck();
// Check to see if we need to send a heartbeat or poll.
bool should_heartbeat_or_poll = network_manager_.HasDataToSend();
// If there's no session data to send, and we don't have a session, then we
// can't send anything.
if (!have_session_data && !session_manager_->HasSession()) {
TLOG(INFO_LEVEL,
"Not sending data since no session and session request in-flight");
} else if (have_session_data || have_registration_data ||
should_heartbeat_or_poll) {
network_manager_.OutboundDataReady();
}
}
void InvalidationClientImpl::Register(const ObjectId& oid) {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
EnsureStarted();
TLOG(INFO_LEVEL, "Received register for %d/%s", oid.source(),
oid.name().c_str());
ObjectIdP object_id;
ConvertToObjectIdProto(oid, &object_id);
registration_manager_->Register(object_id);
}
void InvalidationClientImpl::Unregister(const ObjectId& oid) {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
EnsureStarted();
TLOG(INFO_LEVEL, "Received unregister for %d/%s", oid.source(),
oid.name().c_str());
ObjectIdP object_id;
ConvertToObjectIdProto(oid, &object_id);
registration_manager_->Unregister(object_id);
}
void InvalidationClientImpl::PermanentShutdown() {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
EnsureStarted();
TLOG(INFO_LEVEL, "Doing permanent shutdown by application request");
session_manager_->Shutdown();
}
void InvalidationClientImpl::HandleNewSession() {
// High-level plan:
// - Tell the registration manager we got a new session.
// - Tell the network manager that there was an implicit heartbeat.
// - Attempt to write back the new session token to persistent storage.
// - Inform the application that we have a session.
const string& uniquifier = session_manager_->client_uniquifier();
const string& uniquifier_escaped = EscapeChars(uniquifier);
TLOG(INFO_LEVEL, "Received new session: %s", uniquifier_escaped.c_str());
registration_manager_->HandleNewSession();
network_manager_.RecordImplicitHeartbeat();
TiclState state;
state.set_uniquifier(uniquifier);
state.set_session_token(session_manager_->session_token());
state.set_sequence_number_limit(
registration_manager_->maximum_op_seqno_inclusive());
string serialized;
SerializeState(state, &serialized);
persistence_manager_.WriteState(
serialized,
NewPermanentCallback(
this,
&InvalidationClientImpl::HandleBestEffortWrite));
// Tell the listener we acquired a session and that its registrations were
// removed.
resources_->ScheduleOnListenerThread(
NewPermanentCallback(
listener_,
&InvalidationListener::SessionStatusChanged,
true));
}
void InvalidationClientImpl::HandleLostSession() {
registration_manager_->HandleLostSession();
resources_->ScheduleOnListenerThread(
NewPermanentCallback(
// Tell the listener we lost our session.
listener_, &InvalidationListener::SessionStatusChanged, false));
}
void InvalidationClientImpl::HandleObjectControl(
const ServerToClientMessage& bundle) {
// Handle registration response.
registration_manager_->ProcessInboundMessage(bundle);
// Process invalidations.
for (int i = 0; i < bundle.invalidation_size(); ++i) {
ProcessInvalidation(bundle.invalidation(i));
}
}
void InvalidationClientImpl::HandleInboundMessage(const string& message) {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
EnsureStarted();
if (awaiting_seqno_writeback_) {
// If the initial write back to allocate sequence numbers hasn't returned,
// don't process any messages, since they could cause state changes that
// would require substantial complexity to handle.
TLOG(INFO_LEVEL, "Dropping inbound message since seqno write in-progress");
return;
}
ServerToClientMessage bundle;
bundle.ParseFromString(message);
MessageAction action = session_manager_->ProcessMessage(bundle);
TLOG(INFO_LEVEL, "Classified inbound message as %d", action);
switch (action) {
case IGNORE_MESSAGE:
TLOG(INFO_LEVEL, "Ignored last received message");
return;
case ACQUIRE_SESSION:
HandleNewSession();
break;
case LOSE_CLIENT_ID:
ForgetClientId();
break;
case LOSE_SESSION:
HandleLostSession();
break;
case PROCESS_OBJECT_CONTROL:
HandleObjectControl(bundle);
break;
default:
// Can't happen.
TLOG(INFO_LEVEL, "Unknown message action: %d", action);
return; // Don't process the new polling/heartbeat intervals.
}
// Let the network manager acquire new polling and heartbeat intervals. All
// cases that reach here verified that the message was addressed to this
// client.
network_manager_.HandleInboundMessage(bundle);
}
/* Handles an invalidation. */
void InvalidationClientImpl::ProcessInvalidation(
const InvalidationP& invalidation) {
Closure* callback =
NewPermanentCallback(
this, &InvalidationClientImpl::ScheduleAcknowledgeInvalidation,
invalidation);
const ObjectIdP& oid = invalidation.object_id();
if ((oid.source() == ObjectIdP_Source_INTERNAL) &&
(oid.name().string_value() == INVALIDATE_ALL_OBJECT_NAME)) {
resources_->ScheduleOnListenerThread(
NewPermanentCallback(listener_, &InvalidationListener::InvalidateAll,
callback));
} else {
Invalidation inv;
ConvertFromInvalidationProto(invalidation, &inv);
resources_->ScheduleOnListenerThread(
NewPermanentCallback(listener_, &InvalidationListener::Invalidate, inv,
callback));
}
}
void InvalidationClientImpl::AcknowledgeInvalidation(
const InvalidationP& invalidation) {
MutexLock m(&lock_);
if (invalidation.version() !=
static_cast<uint64>(InvalidationListener::UNKNOWN_OBJECT_VERSION)) {
pending_invalidation_acks_.push_back(invalidation);
network_manager_.OutboundDataReady();
}
}
void InvalidationClientImpl::ScheduleAcknowledgeInvalidation(
const InvalidationP& invalidation) {
resources_->ScheduleImmediately(
NewPermanentCallback(this,
&InvalidationClientImpl::AcknowledgeInvalidation,
invalidation));
}
void InvalidationClientImpl::RegisterOutboundListener(
NetworkCallback* outbound_message_ready) {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
network_manager_.RegisterOutboundListener(outbound_message_ready);
}
void InvalidationClientImpl::TakeOutboundMessage(string* serialized) {
CHECK(!resources_->IsRunningOnInternalThread());
MutexLock m(&lock_);
EnsureStarted();
ClientToServerMessage message;
// If PermanentShutdown() has been called, the session manager will return a
// message of TYPE_SHUTDOWN.
session_manager_->AddSessionAction(&message);
// If the session manager didn't set a message type, then we can let the
// registration manager add fields and set a message type.
if (!message.has_message_type()) {
registration_manager_->AddOutboundData(&message);
} else {
TLOG(INFO_LEVEL, "message had type %d, not giving to reg manager",
message.message_type());
}
// At this point, the message must have a type set.
CHECK(message.has_message_type());
// If the registration manager is sending an OBJECT_CONTROL message, we can
// let the network manager try to attach a heartbeat to it if needed, and we
// can send invalidation acks.
if (message.message_type() ==
ClientToServerMessage_MessageType_TYPE_OBJECT_CONTROL) {
network_manager_.AddHeartbeat(&message);
// Add up to maxRegistrationsPerMessage registrations.
int invalidation_acks_sent = 0;
int registration_count = message.register_operation_size();
// Add any outbound invalidations, up to max_ops_per_message. We ack the
// newest invalidations first (since we pop from the array), which is good,
// because an invalidation for a newer version of an object subsumes an
// older invalidation.
while (!pending_invalidation_acks_.empty() &&
(registration_count + invalidation_acks_sent <
config_.max_ops_per_message)) {
++invalidation_acks_sent;
InvalidationP* inv = message.add_acked_invalidation();
inv->CopyFrom(pending_invalidation_acks_.back());
// If the invalidation contains a component stamp log, add a client stamp.
if (inv->has_component_stamp_log()) {
ComponentStamp* stamp = inv->mutable_component_stamp_log()->add_stamp();
stamp->set_component("C"); // "C" -> Client.
// Internal time value is in microseconds; stamp log should be in
// millis.
stamp->set_time(resources_->current_time().ToInternalValue() /
Time::kMicrosecondsPerMillisecond);
}
pending_invalidation_acks_.pop_back();
}
}
// Regardless, we'll let the network manager add a message id and signal data
// to send.
network_manager_.FinalizeOutboundMessage(&message);
CHECK(message.has_client_type());
message.SerializeToString(serialized);
}
TimeDelta InvalidationClientImpl::SmearDelay(
TimeDelta base_delay, double smear_factor, Random* random) {
CHECK(smear_factor >= 0.0);
CHECK(smear_factor <= 1.0);
// 2*r - 1 gives us a number in [-1, 1]
double normalized_rand = random->RandDouble();
double applied_smear = smear_factor * (2.0 * normalized_rand - 1.0);
return TimeDelta::FromMicroseconds(
static_cast<int64>(
base_delay.InMicroseconds() * (applied_smear + 1.0)));
}
void InvalidationClientImpl::ForgetClientId() {
// Lost client id implies lost session, so take actions for lost session.
HandleLostSession();
// Inform the registration and session managers about the lost client id.
registration_manager_->HandleLostClientId();
session_manager_->DoLoseClientId();
}
void InvalidationClientImpl::EnsureStarted() {
CHECK(is_started_) << "client not started";
}
} // namespace invalidation