blob: 243ca8a525a9e2956787e56a75338bc7ab096bf4 [file]
#include "pool.hpp"
#include "session.hpp"
#include "timer.hpp"
#include "prepare_handler.hpp"
#include "logger.hpp"
#include "set_keyspace_handler.hpp"
namespace cass {
static bool least_busy_comp(Connection* a, Connection* b) {
return a->available_streams() < b->available_streams();
}
Pool::PoolHandler::PoolHandler(Pool* pool, Connection* connection, RequestHandler* request_handler)
: pool_(pool)
, connection_(connection)
, request_handler_(request_handler) { }
void Pool::PoolHandler::on_set(Message* 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(Message* response) {
ResultResponse* result = static_cast<ResultResponse*>(response->body.get());
switch(result->kind) {
case CASS_RESULT_KIND_SET_KEYSPACE:
if(pool_->keyspace_callback_) {
pool_->keyspace_callback_(std::string(result->keyspace, result->keyspace_size));
}
break;
}
request_handler_->on_set(response);
}
void Pool::PoolHandler::on_error_response(Message* response) {
ErrorResponse* error = static_cast<ErrorResponse*>(response->body.get());
switch(error->code) {
case CQL_ERROR_UNPREPARED: {
RequestHandler* request_handler = request_handler_.release();
std::unique_ptr<PrepareHandler> prepare_handler(new PrepareHandler(request_handler));
std::string prepared_id(error->prepared_id, error->prepared_id_size);
if(prepare_handler->init(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, SSLContext* ssl_context, Logger* logger, const Config& config)
: state_(POOL_STATE_NEW)
, host_(host)
, loop_(loop)
, ssl_context_(ssl_context)
, logger_(logger)
, config_(config)
, is_defunct_(false) { }
Pool::~Pool() {
for(auto request_handler : pending_request_queue_) {
if(request_handler->timer) {
Timer::stop(request_handler->timer);
request_handler->timer = nullptr;
request_handler->retry(RETRY_WITH_NEXT_HOST);
}
}
pending_request_queue_.clear();
}
void Pool::connect(const std::string& keyspace) {
if(state_ == POOL_STATE_NEW) {
for (size_t 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 (auto connection : connections_) {
connection->close();
}
for(auto connection : connections_pending_) {
connection->close();
}
maybe_close();
}
}
Connection* Pool::borrow_connection(const std::string& keyspace) {
if(connections_.empty()) {
for (size_t i = 0; i < config_.core_connections_per_host(); ++i) {
spawn_connection(keyspace);
}
return nullptr;
}
maybe_spawn_connection(keyspace);
return find_least_busy();
}
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.
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_NEW || state_ == POOL_STATE_READY) {
Connection* connection = new Connection(loop_,
ssl_context_ ? ssl_context_->session_new() : nullptr,
host_,
logger_,
config_,
keyspace);
connection->set_ready_callback(std::bind(&Pool::on_connection_ready, this, std::placeholders::_1));
connection->set_close_callback(std::bind(&Pool::on_connection_closed, this, std::placeholders::_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() {
ConnectionCollection::iterator it =
std::max_element(connections_.begin(), connections_.end(), least_busy_comp);
if ((*it)->is_ready() && (*it)->available_streams()) {
return *it;
}
return nullptr;
}
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 = nullptr;
}
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);
auto it = std::find(connections_.begin(), connections_.end(), connection);
if(it != connections_.end()) {
connections_.erase(it);
}
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()) {
return false;
}
request_handler->timer = Timer::start(loop_, config_.connect_timeout(),
request_handler,
std::bind(&Pool::on_timeout, this, std::placeholders::_1));
pending_request_queue_.push_back(request_handler);
return true;
}
} // namespace cass