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