blob: a9101744d9e659bda16e3eb4790968a10b6e36a9 [file]
#include "pool.hpp"
#include "session.hpp"
#include "timer.hpp"
#include "prepare_handler.hpp"
#include "logger.hpp"
#include "set_keyspace_handler.hpp"
#include "request_handler.hpp"
#include "connection.hpp"
#include "result_response.hpp"
#include "error_response.hpp"
#include "third_party/boost/boost/bind.hpp"
namespace cass {
static bool least_busy_comp(Connection* a, Connection* b) {
return a->pending_request_count() < b->pending_request_count();
}
Pool::PoolHandler::PoolHandler(Pool* pool, Connection* connection,
RequestHandler* request_handler)
: pool_(pool)
, connection_(connection)
, request_handler_(request_handler) {
}
void Pool::PoolHandler::on_set(ResponseMessage* response) {
switch (response->opcode()) {
case CQL_OPCODE_RESULT:
on_result_response(response);
break;
case CQL_OPCODE_ERROR:
on_error_response(response);
break;
default:
// TODO(mpenick): Log this
request_handler_->on_error(CASS_ERROR_LIB_UNEXPECTED_RESPONSE,
"Unexpected response");
connection_->defunct();
}
finish_request();
}
void Pool::PoolHandler::on_error(CassError code, const std::string& message) {
if (code == CASS_ERROR_LIB_WRITE_ERROR ||
code == CASS_ERROR_UNABLE_TO_SET_KEYSPACE) {
request_handler_.release()->retry(RETRY_WITH_NEXT_HOST);
} else {
request_handler_->on_error(code, message);
}
finish_request();
}
void Pool::PoolHandler::on_timeout() {
request_handler_->on_timeout();
finish_request();
}
void Pool::PoolHandler::finish_request() {
if (connection_->is_ready()) {
pool_->execute_pending_request(connection_);
}
}
void Pool::PoolHandler::on_result_response(ResponseMessage* response) {
ResultResponse* result =
static_cast<ResultResponse*>(response->response_body().get());
switch (result->kind()) {
case CASS_RESULT_KIND_SET_KEYSPACE:
if (pool_->keyspace_callback_) {
pool_->keyspace_callback_(result->keyspace());
}
break;
}
request_handler_->on_set(response);
}
void Pool::PoolHandler::on_error_response(ResponseMessage* response) {
ErrorResponse* error =
static_cast<ErrorResponse*>(response->response_body().get());
switch (error->code()) {
case CQL_ERROR_UNPREPARED: {
RequestHandler* request_handler = request_handler_.release();
ScopedPtr<PrepareHandler> prepare_handler(
new PrepareHandler(request_handler));
if (prepare_handler->init(error->prepared_id())) {
connection_->execute(prepare_handler.release());
} else {
request_handler->on_error(CASS_ERROR_LIB_UNEXPECTED_RESPONSE,
"Received unprepared error for invalid "
"request type or invalid prepared id");
}
break;
}
default:
request_handler_->on_set(response);
break;
}
}
Pool::Pool(const Host& host, uv_loop_t* loop,
Logger* logger, const Config& config)
: state_(POOL_STATE_NEW)
, host_(host)
, loop_(loop)
, logger_(logger)
, config_(config)
, protocol_version_(config.protocol_version())
, is_defunct_(false) {
}
Pool::~Pool() {
for (RequestHandlerList::iterator it = pending_request_queue_.begin(),
end = pending_request_queue_.end();
it != end; ++it) {
RequestHandler* request_handler = *it;
if (request_handler->timer) {
Timer::stop(request_handler->timer);
request_handler->timer = NULL;
request_handler->retry(RETRY_WITH_NEXT_HOST);
}
}
pending_request_queue_.clear();
}
void Pool::connect(const std::string& keyspace) {
if (state_ == POOL_STATE_NEW) {
for (unsigned i = 0; i < config_.core_connections_per_host(); ++i) {
spawn_connection(keyspace);
}
state_ = POOL_STATE_CONNECTING;
}
}
void Pool::close() {
if (state_ != POOL_STATE_CLOSING && state_ != POOL_STATE_CLOSED) {
// We're closing before we've connected (likely beause of an error), we need
// to notify we're "ready"
if (state_ == POOL_STATE_CONNECTING) {
if (ready_callback_) {
ready_callback_(this);
}
}
state_ = POOL_STATE_CLOSING;
for (ConnectionVec::iterator it = connections_.begin(),
end = connections_.end();
it != end; ++it) {
(*it)->close();
}
for (ConnectionSet::iterator it = connections_pending_.begin(),
end = connections_pending_.end();
it != end; ++it) {
(*it)->close();
}
maybe_close();
}
}
Connection* Pool::borrow_connection(const std::string& keyspace) {
if (connections_.empty()) {
for (unsigned i = 0; i < config_.core_connections_per_host(); ++i) {
spawn_connection(keyspace);
}
return NULL;
}
Connection* connection = find_least_busy();
if (connection == NULL ||
connection->pending_request_count() >=
config_.max_simultaneous_requests_threshold()) {
maybe_spawn_connection(keyspace);
}
return connection;
}
bool Pool::execute(Connection* connection, RequestHandler* request_handler) {
PoolHandler* pool_handler =
new PoolHandler(this, connection, request_handler);
if (request_handler->keyspace == connection->keyspace()) {
return connection->execute(pool_handler);
} else {
return connection->execute(new SetKeyspaceHandler(
request_handler->keyspace, connection, pool_handler));
}
}
void Pool::defunct() {
is_defunct_ = true;
close();
}
void Pool::maybe_notify_ready(Connection* connection) {
// Currently, this will notify ready even if all the connections fail.
// This might use some improvement.
// We won't notify until we've tried all valid protocol versions
if (!connection->is_invalid_protocol() ||
connection->protocol_version() <= 1) {
if (state_ == POOL_STATE_CONNECTING && connections_pending_.empty()) {
state_ = POOL_STATE_READY;
if (ready_callback_) {
ready_callback_(this);
}
}
}
}
void Pool::maybe_close() {
if (state_ == POOL_STATE_CLOSING && connections_.empty() &&
connections_pending_.empty()) {
state_ = POOL_STATE_CLOSED;
if (closed_callback_) {
closed_callback_(this);
}
}
}
void Pool::spawn_connection(const std::string& keyspace) {
if (state_ != POOL_STATE_CLOSING && state_ != POOL_STATE_CLOSED) {
Connection* connection =
new Connection(loop_, host_, logger_, config_,
keyspace, protocol_version_);
connection->set_ready_callback(
boost::bind(&Pool::on_connection_ready, this, _1));
connection->set_close_callback(
boost::bind(&Pool::on_connection_closed, this, _1));
connection->connect();
connections_pending_.insert(connection);
}
}
void Pool::maybe_spawn_connection(const std::string& keyspace) {
if (connections_pending_.size() >= config_.max_simultaneous_creation()) {
return;
}
if (connections_.size() + connections_pending_.size() >=
config_.max_connections_per_host()) {
return;
}
spawn_connection(keyspace);
}
Connection* Pool::find_least_busy() {
ConnectionVec::iterator it = std::min_element(
connections_.begin(), connections_.end(), least_busy_comp);
if ((*it)->is_ready() && (*it)->available_streams() > 0) {
return *it;
}
return NULL;
}
void Pool::execute_pending_request(Connection* connection) {
if (!pending_request_queue_.empty()) {
RequestHandler* request_handler = pending_request_queue_.front();
pending_request_queue_.pop_front();
if (request_handler->timer) {
Timer::stop(request_handler->timer);
request_handler->timer = NULL;
}
if (!execute(connection, request_handler)) {
request_handler->retry(RETRY_WITH_NEXT_HOST);
}
}
}
void Pool::on_connection_ready(Connection* connection) {
connections_pending_.erase(connection);
maybe_notify_ready(connection);
connections_.push_back(connection);
execute_pending_request(connection);
}
void Pool::on_connection_closed(Connection* connection) {
connections_pending_.erase(connection);
maybe_notify_ready(connection);
ConnectionVec::iterator it =
std::find(connections_.begin(), connections_.end(), connection);
if (it != connections_.end()) {
connections_.erase(it);
}
if (connection->is_invalid_protocol() &&
connection->protocol_version() > 1) {
protocol_version_ = connection->protocol_version() - 1;
spawn_connection(connection->keyspace());
} else if (connection->is_defunct()) {
// TODO(mpenick): Conviction policy
defunct();
}
maybe_close();
}
void Pool::on_timeout(Timer* timer) {
RequestHandler* request_handler = static_cast<RequestHandler*>(timer->data());
pending_request_queue_.remove(request_handler);
request_handler->retry(RETRY_WITH_NEXT_HOST);
maybe_close();
}
bool Pool::wait_for_connection(RequestHandler* request_handler) {
if (pending_request_queue_.size() + 1 > config_.max_pending_requests()) {
logger_->warn("Exceeded the max pending requests setting of %u on '%s'",
config_.max_pending_requests(),
host_.address.to_string().c_str());
return false;
}
request_handler->timer =
Timer::start(loop_, config_.connect_timeout(), request_handler,
boost::bind(&Pool::on_timeout, this, _1));
pending_request_queue_.push_back(request_handler);
return true;
}
} // namespace cass