blob: a92b5e075cab2cd27861801ef7c46eb048ead79c [file]
/*
Copyright (c) 2014 DataStax
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 "io_worker.hpp"
#include "config.hpp"
#include "pool.hpp"
#include "request_handler.hpp"
#include "logger.hpp"
#include "session.hpp"
#include "timer.hpp"
#include "third_party/boost/boost/bind.hpp"
namespace cass {
IOWorker::IOWorker(Session* session, Logger* logger, const Config& config)
: session_(session)
, logger_(logger)
, is_closing_(false)
, pending_request_count_(0)
, config_(config)
, request_queue_(config.queue_size_io()) {
prepare_.data = this;
}
IOWorker::~IOWorker() {
cleanup();
}
int IOWorker::init() {
int rc = EventThread<IOWorkerEvent>::init(config_.queue_size_event());
if (rc != 0) return rc;
rc = request_queue_.init(loop(), this, &IOWorker::on_execute);
if (rc != 0) return rc;
rc = uv_prepare_init(loop(), &prepare_);
if (rc != 0) return rc;
rc = uv_prepare_start(&prepare_, on_prepare);
return rc;
}
bool IOWorker::add_pool_async(Host host) {
IOWorkerEvent event;
event.type = IOWorkerEvent::ADD_POOL;
event.host = host;
return send_event_async(event);
}
bool IOWorker::remove_pool_async(Host host) {
IOWorkerEvent event;
event.type = IOWorkerEvent::REMOVE_POOL;
event.host = host;
return send_event_async(event);
}
void IOWorker::close_async() {
while (!request_queue_.enqueue(NULL)) {
// Keep trying
}
}
void IOWorker::add_pool(Host host) {
if (!is_closing_ && pools.count(host) == 0) {
Pool* pool = new Pool(host, loop(), logger_, config_);
pool->set_ready_callback(boost::bind(&IOWorker::on_pool_ready, this, _1));
pool->set_closed_callback(boost::bind(&IOWorker::on_pool_closed, this, _1));
pool->set_keyspace_callback(
boost::bind(&IOWorker::on_set_keyspace, this, _1));
pool->connect(session_->keyspace());
pools[host] = pool;
}
}
bool IOWorker::execute(RequestHandler* request_handler) {
return request_queue_.enqueue(request_handler);
}
void IOWorker::on_set_keyspace(const std::string& keyspace) {
session_->set_keyspace(keyspace);
}
void IOWorker::maybe_close() {
if (is_closing_ && pending_request_count_ <= 0) {
for (PoolMap::iterator it = pools.begin(), end = pools.end(); it != end;
++it) {
it->second->close();
}
maybe_notify_closed();
}
}
void IOWorker::maybe_notify_closed() {
if (pools.empty()) {
session_->notify_closed_async();
close_handles();
}
}
void IOWorker::cleanup() {
PoolList::iterator it = pending_delete_.begin();
while (it != pending_delete_.end()) {
delete *it;
it = pending_delete_.erase(it);
}
}
void IOWorker::close_handles() {
EventThread<IOWorkerEvent>::close_handles();
request_queue_.close_handles();
uv_prepare_stop(&prepare_);
uv_close(reinterpret_cast<uv_handle_t*>(&prepare_), NULL);
while (!pending_reconnects_.is_empty()) {
ReconnectRequest* reconnect_request = pending_reconnects_.front();
reconnect_request->stop_timer();
pending_reconnects_.remove(reconnect_request);
delete reconnect_request;
}
logger_->debug("IO worker active handles %d", loop()->active_handles);
}
void IOWorker::on_pool_ready(Pool* pool) {
session_->notify_ready_async();
}
void IOWorker::on_pool_closed(Pool* pool) {
Host host = pool->host();
logger_->info("Pool for '%s' closed", host.address.to_string().c_str());
pending_delete_.push_back(pool);
pools.erase(host);
if (is_closing_) {
maybe_notify_closed();
} else {
ReconnectRequest* reconnect_request = new ReconnectRequest(this, host);
pending_reconnects_.add_to_back(reconnect_request);
reconnect_request->timer = Timer::start(loop(),
config_.reconnect_wait(),
reconnect_request,
IOWorker::on_pool_reconnect);
}
}
void IOWorker::on_retry(RequestHandler* request_handler, RetryType retry_type) {
Host host;
if (retry_type == RETRY_WITH_NEXT_HOST) {
request_handler->next_host();
}
if (!request_handler->get_current_host(&host)) {
request_handler->on_error(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE,
"No hosts available");
delete request_handler;
return;
}
PoolMap::iterator it = pools.find(host);
if (it != pools.end()) {
Pool* pool = it->second;
Connection* connection = pool->borrow_connection(request_handler->keyspace);
if (connection != NULL) {
if (!pool->execute(connection, request_handler)) {
on_retry(request_handler, RETRY_WITH_NEXT_HOST);
}
} else { // Too busy, or no connections
if (!pool->wait_for_connection(request_handler)) {
on_retry(request_handler, RETRY_WITH_NEXT_HOST);
}
}
} else {
on_retry(request_handler, RETRY_WITH_NEXT_HOST);
}
}
void IOWorker::on_request_finished(RequestHandler* request_handler) {
pending_request_count_--;
maybe_close();
}
void IOWorker::on_event(const IOWorkerEvent& event) {
if (event.type == IOWorkerEvent::ADD_POOL) {
add_pool(event.host);
} else if (event.type == IOWorkerEvent::REMOVE_POOL) {
// TODO(mpenick):
}
}
void IOWorker::on_pool_reconnect(Timer* timer) {
ReconnectRequest* reconnect_request =
static_cast<ReconnectRequest*>(timer->data());
IOWorker* io_worker = reconnect_request->io_worker;
if (!io_worker->is_closing_) {
io_worker->logger_->info(
"Attempting to reconnect to '%s'",
reconnect_request->host.address.to_string().c_str());
io_worker->add_pool(reconnect_request->host);
}
io_worker->pending_reconnects_.remove(reconnect_request);
delete reconnect_request;
}
void IOWorker::on_execute(uv_async_t* async, int status) {
IOWorker* io_worker = reinterpret_cast<IOWorker*>(async->data);
RequestHandler* request_handler = NULL;
while (io_worker->request_queue_.dequeue(request_handler)) {
if (request_handler != NULL) {
io_worker->pending_request_count_++;
request_handler->set_retry_callback(
boost::bind(&IOWorker::on_retry, io_worker, _1, _2));
request_handler->set_finished_callback(
boost::bind(&IOWorker::on_request_finished, io_worker, _1));
request_handler->retry(RETRY_WITH_CURRENT_HOST);
} else {
io_worker->is_closing_ = true;
}
}
io_worker->maybe_close();
}
void IOWorker::on_prepare(uv_prepare_t* prepare, int status) {
IOWorker* io_worker = reinterpret_cast<IOWorker*>(prepare->data);
io_worker->cleanup();
}
void IOWorker::ReconnectRequest::stop_timer() {
assert(timer != NULL);
Timer::stop(timer);
timer = NULL;
}
} // namespace cass