blob: 924700d94d66407dd988d3300f0ed6c69abdde34 [file]
#include "connection.hpp"
#include "constants.hpp"
#include "response.hpp"
#include "connecter.hpp"
#include "timer.hpp"
#include "config.hpp"
#include "result_response.hpp"
#include "supported_response.hpp"
#include "startup_request.hpp"
#include "query_request.hpp"
#include "options_request.hpp"
#include "error_response.hpp"
#include "logger.hpp"
#include "cassandra.h"
#include <sstream>
#include <iomanip>
namespace cass {
Connection::StartupHandler::StartupHandler(Connection* connection,
Request* request)
: connection_(connection)
, request_(request) {
}
const Request* Connection::StartupHandler::request() const {
return request_.get();
}
void Connection::StartupHandler::on_set(ResponseMessage* response) {
switch (response->opcode()) {
case CQL_OPCODE_SUPPORTED:
connection_->on_supported(response);
break;
case CQL_OPCODE_ERROR: {
ErrorResponse* error
= static_cast<ErrorResponse*>(response->response_body().get());
if (error->code() == CQL_ERROR_PROTOCOL_ERROR &&
error->message().find("Invalid or unsupported protocol version") != std::string::npos) {
connection_->is_invalid_protocol_ = true;
connection_->logger_->warn(
"Protocol version %d unsupported. Trying protocol version %d...",
connection_->protocol_version_, connection_->protocol_version_ - 1);
} else {
std::ostringstream ss;
ss << "Error response during startup: '" << error->message()
<< "' (0x" << std::hex << std::uppercase << std::setw(8) << std::setfill('0')
<< CASS_ERROR(CASS_ERROR_SOURCE_SERVER, error->code()) << ")";
connection_->notify_error(ss.str());
}
}
break;
case CQL_OPCODE_READY:
connection_->on_ready();
break;
case CQL_OPCODE_RESULT:
on_result_response(response);
break;
default:
connection_->notify_error("Invalid opcode during startup");
break;
}
}
void Connection::StartupHandler::on_error(CassError code,
const std::string& message) {
std::ostringstream ss;
ss << "Error during startup: '" << message
<< "' (0x" << std::hex << std::uppercase << std::setw(8) << std::setfill('0') << code << ")";
connection_->notify_error(ss.str());
}
void Connection::StartupHandler::on_timeout() {
if (!connection_->is_closing()) {
connection_->notify_error("Timed out during startup");
}
}
void Connection::StartupHandler::on_result_response(ResponseMessage* response) {
ResultResponse* result =
static_cast<ResultResponse*>(response->response_body().get());
switch (result->kind()) {
case CASS_RESULT_KIND_SET_KEYSPACE:
connection_->on_set_keyspace();
break;
default:
connection_->notify_error(
"Invalid result during startup. Expected set keyspace.");
break;
}
}
Connection::InternalRequest::InternalRequest(Connection* connection)
: connection(connection)
, stream_(0)
, timer_(NULL)
, state_(REQUEST_STATE_NEW) {
}
void Connection::InternalRequest::on_set(ResponseMessage* response) {
switch (response->opcode()) {
case CQL_OPCODE_RESULT:
on_result_response(response);
break;
}
response_callback_->on_set(response);
}
void Connection::InternalRequest::on_error(CassError code, const std::string& message) {
response_callback_->on_error(code, message);
connection->stream_manager_.release_stream(stream_);
}
void Connection::InternalRequest::on_timeout() {
response_callback_->on_timeout();
}
void Connection::InternalRequest::change_state(Connection::InternalRequest::State next_state) {
switch (state_) {
case REQUEST_STATE_NEW:
assert(next_state == REQUEST_STATE_WRITING &&
"Invalid request state after new");
state_ = REQUEST_STATE_WRITING;
timer_ =
Timer::start(connection->loop_, connection->config_.write_timeout(),
this, on_request_timeout);
break;
case REQUEST_STATE_WRITING:
if (next_state == REQUEST_STATE_READING) { // Success
stop_timer();
state_ = next_state;
timer_ =
Timer::start(connection->loop_, connection->config_.read_timeout(),
this, on_request_timeout);
} else if (next_state == REQUEST_STATE_READ_BEFORE_WRITE ||
next_state == REQUEST_STATE_DONE) {
stop_timer();
state_ = next_state;
} else if (next_state == REQUEST_STATE_WRITE_TIMEOUT) {
connection->timed_out_request_count_++;
state_ = next_state;
} else {
assert(false && "Invalid request state after writing");
}
break;
case REQUEST_STATE_READING:
if (next_state == REQUEST_STATE_DONE) { // Success
stop_timer();
state_ = next_state;
} else if (next_state == REQUEST_STATE_READ_TIMEOUT) {
connection->timed_out_request_count_++;
state_ = next_state;
} else {
assert(false && "Invalid request state after reading");
}
break;
case REQUEST_STATE_WRITE_TIMEOUT:
assert((next_state == REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ ||
next_state == REQUEST_STATE_READ_BEFORE_WRITE) &&
"Invalid request state after write timeout");
state_ = next_state;
break;
case REQUEST_STATE_READ_TIMEOUT:
assert(next_state == REQUEST_STATE_DONE &&
"Invalid request state after read timeout");
connection->timed_out_request_count_--;
state_ = next_state;
break;
case REQUEST_STATE_READ_BEFORE_WRITE:
assert(next_state == REQUEST_STATE_DONE &&
"Invalid request state after read before write");
state_ = next_state;
break;
case REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ:
assert(next_state == REQUEST_STATE_DONE &&
"Invalid request state after write timeout before read");
connection->timed_out_request_count_--;
state_ = next_state;
break;
case REQUEST_STATE_DONE:
assert(false && "Invalid request state after done");
break;
default:
assert(false && "Invalid request state");
break;
}
}
void Connection::InternalRequest::stop_timer() {
assert(timer_ != NULL);
Timer::stop(timer_);
timer_ = NULL;
}
void Connection::InternalRequest::on_result_response(ResponseMessage* response) {
ResultResponse* result =
static_cast<ResultResponse*>(response->response_body().get());
switch (result->kind()) {
case CASS_RESULT_KIND_SET_KEYSPACE:
connection->keyspace_ = result->keyspace();
break;
}
}
void Connection::InternalRequest::on_request_timeout(Timer* timer) {
InternalRequest* request = static_cast<InternalRequest*>(timer->data());
request->connection->logger_->info("Request timed out to '%s'",
request->connection->host_string_.c_str());
request->timer_ = NULL;
if (request->state_ == REQUEST_STATE_READING) {
request->change_state(REQUEST_STATE_READ_TIMEOUT);
} else if (request->state_ == REQUEST_STATE_WRITING) {
request->change_state(REQUEST_STATE_WRITE_TIMEOUT);
} else {
assert(false && "Invalid request state for timeout");
}
request->on_timeout();
}
Connection::Connection(uv_loop_t* loop,
const Host& host, Logger* logger, const Config& config,
const std::string& keyspace, int protocol_version)
: state_(CLIENT_STATE_NEW)
, is_defunct_(false)
, is_invalid_protocol_(false)
, timed_out_request_count_(0)
, loop_(loop)
, response_(new ResponseMessage())
, host_(host)
, host_string_(host.address.to_string())
, ssl_handshake_done_(false)
, version_("3.0.0")
, protocol_version_(protocol_version)
, logger_(logger)
, config_(config)
, keyspace_(keyspace)
, connect_timer_(NULL) {
socket_.data = this;
uv_tcp_init(loop_, &socket_);
}
void Connection::connect() {
if (state_ == CLIENT_STATE_NEW) {
state_ = CLIENT_STATE_CONNECTING;
connect_timer_ = Timer::start(loop_, config_.connect_timeout(), this,
on_connect_timeout);
Connecter::connect(&socket_, host_.address, this, on_connect);
}
}
bool Connection::execute(ResponseCallback* response_callback) {
ScopedPtr<InternalRequest> internal_request(new InternalRequest(this));
const Request* request = response_callback->request();
int8_t stream = stream_manager_.acquire_stream(internal_request.get());
if (stream < 0) {
return false;
}
internal_request->set_stream(stream);
internal_request->set_response_callback(response_callback);
ScopedPtr<BufferVec> bufs(request->encode(protocol_version_, 0x00, stream));
if (!bufs) {
internal_request->on_error(CASS_ERROR_LIB_MESSAGE_ENCODE,
"Operation unsupported by this protocol version");
return true; // Don't retry
}
logger_->debug("Sending message type %s with %d",
opcode_to_string(request->opcode()).c_str(), stream);
pending_requests_.add_to_back(internal_request.get());
internal_request->change_state(InternalRequest::REQUEST_STATE_WRITING);
write(bufs.release(), internal_request.release());
return true;
}
void Connection::close() {
if (state_ != CLIENT_STATE_CLOSING && state_ != CLIENT_STATE_CLOSED) {
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(&socket_))) {
if (state_ >= CLIENT_STATE_CONNECTED) {
uv_read_stop(reinterpret_cast<uv_stream_t*>(&socket_));
}
state_ = CLIENT_STATE_CLOSING;
uv_close(reinterpret_cast<uv_handle_t*>(&socket_), on_close);
}
}
}
void Connection::defunct() {
is_defunct_ = true;
close();
}
void Connection::write(BufferVec* bufs, Connection::InternalRequest* request) {
uv_stream_t* stream = reinterpret_cast<uv_stream_t*>(&socket_);
Writer::write(stream, bufs, request, on_write);
}
void Connection::event_received() {
switch (state_) {
case CLIENT_STATE_CONNECTED:
ssl_handshake();
break;
case CLIENT_STATE_HANDSHAKE:
send_options();
break;
case CLIENT_STATE_SUPPORTED:
send_startup();
break;
case CLIENT_STATE_READY:
notify_ready();
break;
case CLIENT_STATE_SET_KEYSPACE:
send_use_keyspace();
break;
case CLIENT_STATE_CLOSED:
break;
default:
assert(false);
}
}
void Connection::consume(char* input, size_t size) {
char* buffer = input;
int remaining = size;
while (remaining != 0) {
int consumed = response_->decode(protocol_version_, buffer, remaining);
if (consumed < 0) {
// TODO(mstump) probably means connection closed/failed
// Can this even happen right now?
logger_->error("Error consuming message on '%s'", host_string_.c_str());
}
if (response_->is_body_ready()) {
ScopedPtr<ResponseMessage> response(response_.release());
response_.reset(new ResponseMessage());
logger_->debug(
"Consumed message type %s with stream %d, input %lu, remaining %d on "
"'%s'",
opcode_to_string(response->opcode()).c_str(), static_cast<int>(response->stream()),
size, remaining, host_string_.c_str());
if (response->stream() < 0) {
// TODO(mstump) system events
assert(false);
} else {
InternalRequest* request = NULL;
if (stream_manager_.get_item(response->stream(), request)) {
switch (request->state()) {
case InternalRequest::REQUEST_STATE_READING:
request->on_set(response.get());
request->change_state(InternalRequest::REQUEST_STATE_DONE);
break;
case InternalRequest::REQUEST_STATE_WRITING:
request->on_set(response.get());
request->change_state(InternalRequest::REQUEST_STATE_READ_BEFORE_WRITE);
break;
case InternalRequest::REQUEST_STATE_WRITE_TIMEOUT:
request->change_state(InternalRequest::REQUEST_STATE_READ_BEFORE_WRITE);
break;
case InternalRequest::REQUEST_STATE_READ_TIMEOUT:
request->change_state(InternalRequest::REQUEST_STATE_DONE);
break;
case InternalRequest::REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ:
request->change_state(InternalRequest::REQUEST_STATE_DONE);
break;
default:
assert(false && "Invalid request state after receiving response");
break;
}
if (request->state() == InternalRequest::REQUEST_STATE_DONE) {
pending_requests_.remove(request);
delete request;
}
} else {
logger_->error("Invalid stream returnd from server on '%s'",
host_string_.c_str());
defunct();
}
}
}
remaining -= consumed;
buffer += consumed;
}
}
void Connection::on_connect(Connecter* connecter) {
Connection* connection = reinterpret_cast<Connection*>(connecter->data());
if (connection->is_defunct()) {
return; // Timed out
}
Timer::stop(connection->connect_timer_);
connection->connect_timer_ = NULL;
if (connecter->status() == Connecter::SUCCESS) {
connection->logger_->debug("Connected to '%s'",
connection->host_string_.c_str());
uv_read_start(reinterpret_cast<uv_stream_t*>(&connection->socket_),
alloc_buffer, on_read);
connection->state_ = CLIENT_STATE_CONNECTED;
connection->event_received();
} else {
connection->logger_->info("Connect error '%s' on '%s'",
connection->host_string_.c_str(),
uv_err_name(uv_last_error(connection->loop_)));
connection->notify_error("Unable to connect");
}
}
void Connection::on_connect_timeout(Timer* timer) {
Connection* connection = reinterpret_cast<Connection*>(timer->data());
connection->connect_timer_ = NULL;
connection->notify_error("Connection timeout");
}
void Connection::on_close(uv_handle_t* handle) {
Connection* connection = reinterpret_cast<Connection*>(handle->data);
connection->logger_->debug("Connection to '%s' closed",
connection->host_string_.c_str());
while (!connection->pending_requests_.is_empty()) {
InternalRequest* request = connection->pending_requests_.front();
if (request->state() == InternalRequest::REQUEST_STATE_WRITING ||
request->state() == InternalRequest::REQUEST_STATE_READING) {
request->on_timeout();
request->stop_timer();
}
connection->pending_requests_.remove(request);
delete request;
}
connection->state_ = CLIENT_STATE_CLOSED;
connection->event_received();
if (connection->closed_callback_) {
connection->closed_callback_(connection);
}
delete connection;
}
void Connection::on_read(uv_stream_t* client, ssize_t nread, uv_buf_t buf) {
Connection* connection = reinterpret_cast<Connection*>(client->data);
if (nread == -1) {
if (uv_last_error(connection->loop_).code != UV_EOF) {
connection->logger_->info("Read error '%s' on '%s'",
connection->host_string_.c_str(),
uv_err_name(uv_last_error(connection->loop_)));
}
connection->defunct();
free_buffer(buf);
return;
}
connection->consume(buf.base, nread);
free_buffer(buf);
}
void Connection::on_write(Writer* writer) {
InternalRequest* request = static_cast<InternalRequest*>(writer->data());
Connection* connection = request->connection;
switch (request->state()) {
case InternalRequest::REQUEST_STATE_WRITING:
if (writer->status() == Writer::SUCCESS) {
request->change_state(InternalRequest::REQUEST_STATE_READING);
} else {
if (!connection->is_closing()) {
connection->logger_->info(
"Write error '%s' on '%s'", connection->host_string_.c_str(),
uv_err_name(uv_last_error(connection->loop_)));
connection->defunct();
}
request->on_error(CASS_ERROR_LIB_WRITE_ERROR,
"Unable to write to socket");
request->change_state(InternalRequest::REQUEST_STATE_DONE);
}
break;
case InternalRequest::REQUEST_STATE_WRITE_TIMEOUT:
request->change_state(InternalRequest::REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ);
break;
case InternalRequest::REQUEST_STATE_READ_BEFORE_WRITE:
request->change_state(InternalRequest::REQUEST_STATE_DONE);
break;
default:
assert(false && "Invalid request state after write finished");
break;
}
if (request->state() == InternalRequest::REQUEST_STATE_DONE) {
connection->pending_requests_.remove(request);
delete request;
}
}
void Connection::ssl_handshake() {
state_ = CLIENT_STATE_HANDSHAKE;
event_received();
}
void Connection::on_ready() {
if (keyspace_.empty()) {
state_ = CLIENT_STATE_READY;
} else {
state_ = CLIENT_STATE_SET_KEYSPACE;
}
event_received();
}
void Connection::on_set_keyspace() {
state_ = CLIENT_STATE_READY;
event_received();
}
void Connection::on_supported(ResponseMessage* response) {
SupportedResponse* supported =
static_cast<SupportedResponse*>(response->response_body().get());
// TODO(mstump) do something with the supported info
(void)supported;
state_ = CLIENT_STATE_SUPPORTED;
event_received();
}
void Connection::notify_ready() {
if (ready_callback_) {
ready_callback_(this);
}
}
void Connection::notify_error(const std::string& error) {
logger_->error("'%s' error on startup for '%s'", error.c_str(),
host_string_.c_str());
defunct();
}
void Connection::send_options() {
execute(new StartupHandler(this, new OptionsRequest()));
}
void Connection::send_startup() {
StartupRequest* startup = new StartupRequest();
startup->set_version("3.0.0");
execute(new StartupHandler(this, startup));
}
void Connection::send_use_keyspace() {
QueryRequest* query = new QueryRequest();
query->set_query("use \"" + keyspace_ + "\"");
query->set_consistency(CASS_CONSISTENCY_ONE);
execute(new StartupHandler(this, query));
}
} // namespace cass