| /* |
| 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 "session.hpp" |
| #include "types.hpp" |
| #include "config.hpp" |
| #include "request_handler.hpp" |
| #include "logger.hpp" |
| #include "round_robin_policy.hpp" |
| #include "resolver.hpp" |
| #include "io_worker.hpp" |
| #include "prepare_request.hpp" |
| |
| extern "C" { |
| |
| void cass_session_free(CassSession* session) { |
| delete session->from(); |
| } |
| |
| CassFuture* cass_session_close(CassSession* session) { |
| // TODO(mpenick): Make sure this handles close during the middle of a connect |
| cass::SessionCloseFuture* close_future = new cass::SessionCloseFuture(); |
| session->close_async(close_future); |
| return CassFuture::to(close_future); |
| } |
| |
| CassFuture* cass_session_prepare(CassSession* session, CassString statement) { |
| return CassFuture::to(session->prepare(statement.data, statement.length)); |
| } |
| |
| CassFuture* cass_session_execute(CassSession* session, |
| const CassStatement* statement) { |
| return CassFuture::to(session->execute(statement->from())); |
| } |
| |
| CassFuture* cass_session_execute_batch(CassSession* session, const CassBatch* batch) { |
| return CassFuture::to(session->execute(batch->from())); |
| } |
| |
| } // extern "C" |
| |
| namespace cass { |
| |
| Session::Session(const Config& config) |
| : connect_future_(NULL) |
| , close_future_(NULL) |
| , config_(config) |
| , load_balancing_policy_(new RoundRobinPolicy()) |
| , pending_resolve_count_(0) |
| , pending_pool_count_(0) |
| , pending_workers_count_(0) |
| , current_io_worker_(0) { |
| uv_mutex_init(&keyspace_mutex_); |
| } |
| |
| Session::~Session() { |
| uv_mutex_destroy(&keyspace_mutex_); |
| if (connect_future_ != NULL) { |
| connect_future_->release(); |
| } |
| for (IOWorkerVec::iterator it = io_workers_.begin(), end = io_workers_.end(); |
| it != end; ++it) { |
| delete (*it); |
| } |
| } |
| |
| int Session::init() { |
| int rc = EventThread<SessionEvent>::init(config_.queue_size_event()); |
| if (rc != 0) return rc; |
| request_queue_.reset( |
| new AsyncQueue<MPMCQueue<RequestHandler*> >(config_.queue_size_io())); |
| rc = request_queue_->init(loop(), this, &Session::on_execute); |
| if (rc != 0) return rc; |
| for (unsigned i = 0; i < config_.thread_count_io(); ++i) { |
| ScopedPtr<IOWorker> io_worker(new IOWorker(this, logger_.get(), config_)); |
| int rc = io_worker->init(); |
| if (rc != 0) return rc; |
| io_workers_.push_back(io_worker.release()); |
| } |
| return rc; |
| } |
| |
| bool Session::notify_ready_async() { |
| SessionEvent event; |
| event.type = SessionEvent::NOTIFY_READY; |
| return send_event_async(event); |
| } |
| |
| bool Session::notify_closed_async() { |
| SessionEvent event; |
| event.type = SessionEvent::NOTIFY_CLOSED; |
| return send_event_async(event); |
| } |
| |
| bool Session::connect_async(const std::string& keyspace, Future* future) { |
| logger_.reset(new Logger(config_)); |
| if (logger_->init() != 0 || init() != 0) { |
| return false; |
| } |
| |
| SessionEvent event; |
| event.type = SessionEvent::CONNECT; |
| if (!send_event_async(event)) { |
| return false; |
| } |
| |
| if (!keyspace.empty()) { |
| set_keyspace(keyspace); |
| } |
| |
| connect_future_ = future; |
| connect_future_->retain(); |
| |
| run(); |
| |
| return true; |
| } |
| |
| void Session::close_async(Future* future) { |
| close_future_ = future; |
| if (close_future_ != NULL) { |
| close_future_->retain(); |
| } |
| while (!request_queue_->enqueue(NULL)) { |
| // Keep trying |
| } |
| } |
| |
| void Session::init_pools() { |
| if (hosts_.empty()) { |
| connect_future_->set_error(CASS_ERROR_LIB_NO_HOSTS_AVAILABLE, |
| "No hosts provided or no hosts resolved"); |
| connect_future_ = NULL; |
| return; |
| } |
| |
| pending_pool_count_ = hosts_.size() * io_workers_.size(); |
| for (HostSet::iterator hosts_it = hosts_.begin(), hosts_end = hosts_.end(); |
| hosts_it != hosts_end; ++hosts_it) { |
| for (IOWorkerVec::iterator it = io_workers_.begin(), |
| end = io_workers_.end(); |
| it != end; ++it) { |
| (*it)->add_pool_async(*hosts_it); |
| } |
| } |
| } |
| |
| void Session::close_handles() { |
| EventThread<SessionEvent>::close_handles(); |
| request_queue_->close_handles(); |
| } |
| |
| void Session::on_run() { |
| if (config_.log_level() != CASS_LOG_DISABLED) { |
| logger_->run(); |
| } |
| for (IOWorkerVec::iterator it = io_workers_.begin(), end = io_workers_.end(); |
| it != end; ++it) { |
| (*it)->run(); |
| } |
| } |
| |
| void Session::on_after_run() { |
| for (IOWorkerVec::iterator it = io_workers_.begin(), end = io_workers_.end(); |
| it != end; ++it) { |
| (*it)->join(); |
| } |
| logger_->join(); |
| |
| Future* close_future = close_future_; |
| |
| // Let the session thread handle the final cleanup of the session. At this |
| // point we know that no other code is using this object. |
| delete this; |
| |
| if (close_future != NULL) { |
| close_future->set(); |
| } |
| } |
| |
| void Session::on_event(const SessionEvent& event) { |
| if (event.type == SessionEvent::CONNECT) { |
| int port = config_.port(); |
| |
| const Config::ContactPointList& contact_points = config_.contact_points(); |
| for (Config::ContactPointList::const_iterator it = contact_points.begin(), |
| end = contact_points.end(); |
| it != end; ++it) { |
| const std::string& seed = *it; |
| Address address; |
| if (Address::from_string(seed, port, &address)) { |
| Host host(address); |
| hosts_.insert(host); |
| } else { |
| pending_resolve_count_++; |
| Resolver::resolve(loop(), seed, port, this, on_resolve); |
| } |
| } |
| |
| if (pending_resolve_count_ == 0) { |
| init_pools(); |
| } |
| } else if (event.type == SessionEvent::NOTIFY_READY) { |
| if (--pending_pool_count_ == 0) { |
| logger_->debug("Session is connected"); |
| load_balancing_policy_->init(hosts_); |
| connect_future_->set(); |
| connect_future_ = NULL; |
| } |
| logger_->debug("Session pending pool count %d", pending_pool_count_); |
| } else if (event.type == SessionEvent::NOTIFY_CLOSED) { |
| if (--pending_workers_count_ == 0) { |
| logger_->close_async(); |
| close_handles(); |
| } |
| } |
| } |
| |
| void Session::on_resolve(Resolver* resolver) { |
| Session* session = reinterpret_cast<Session*>(resolver->data()); |
| if (resolver->is_success()) { |
| Host host(resolver->address()); |
| session->hosts_.insert(host); |
| } else { |
| session->logger_->error("Unable to resolve %s:%d\n", |
| resolver->host().c_str(), resolver->port()); |
| } |
| if (--session->pending_resolve_count_ == 0) { |
| session->init_pools(); |
| } |
| } |
| |
| void Session::execute(RequestHandler* request_handler) { |
| if (!request_queue_->enqueue(request_handler)) { |
| request_handler->on_error(CASS_ERROR_LIB_REQUEST_QUEUE_FULL, |
| "The request queue has reached capacity"); |
| delete request_handler; |
| } |
| } |
| |
| Future* Session::prepare(const char* statement, size_t length) { |
| PrepareRequest* prepare = new PrepareRequest(); |
| prepare->set_query(statement, length); |
| RequestHandler* request_handler = new RequestHandler(prepare); |
| ResponseFuture* future = request_handler->future(); |
| future->statement.assign(statement, length); |
| execute(request_handler); |
| return future; |
| } |
| |
| Future* Session::execute(const Request* statement) { |
| RequestHandler* request_handler = new RequestHandler(statement); |
| Future* future = request_handler->future(); |
| execute(request_handler); |
| return future; |
| } |
| |
| void Session::on_execute(uv_async_t* data, int status) { |
| Session* session = reinterpret_cast<Session*>(data->data); |
| |
| bool is_closing = false; |
| |
| RequestHandler* request_handler = NULL; |
| while (session->request_queue_->dequeue(request_handler)) { |
| if (request_handler != NULL) { |
| request_handler->keyspace = session->keyspace(); |
| session->load_balancing_policy_->new_query_plan(&request_handler->hosts); |
| |
| size_t start = session->current_io_worker_; |
| size_t remaining = session->io_workers_.size(); |
| const size_t size = session->io_workers_.size(); |
| while (remaining != 0) { |
| // TODO(mpenick): Make this something better than RR |
| IOWorker* io_worker = session->io_workers_[start % size]; |
| if (io_worker->execute(request_handler)) { |
| session->current_io_worker_ = (start + 1) % size; |
| break; |
| } |
| start++; |
| remaining--; |
| } |
| |
| if (remaining == 0) { |
| request_handler->on_error(CASS_ERROR_LIB_NO_AVAILABLE_IO_THREAD, |
| "All workers are busy"); |
| } |
| } else { |
| is_closing = true; |
| } |
| } |
| |
| if (is_closing) { |
| session->pending_workers_count_ = session->io_workers_.size(); |
| for (IOWorkerVec::iterator it = session->io_workers_.begin(), |
| end = session->io_workers_.end(); |
| it != end; ++it) { |
| (*it)->close_async(); |
| } |
| } |
| } |
| |
| } // namespace cass |