blob: cf2e222517811fdacc207de208482ed4329b1f75 [file]
#include "connection.hpp"
namespace cass {
Connection::StartupHandler::StartupHandler(Connection* connection, Message* request)
: connection_(connection)
, request_(request) { }
Message*Connection::StartupHandler::request() const {
return request_.get();
}
void Connection::StartupHandler::on_set(Message* response) {
switch(response->opcode) {
case CQL_OPCODE_SUPPORTED:
connection_->on_supported(response);
break;
case CQL_OPCODE_ERROR:
connection_->notify_error("Error during startup"); // TODO(mpenick): Better error
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) {
connection_->notify_error("Error during startup");
}
void Connection::StartupHandler::on_timeout() {
connection_->notify_error("Timed out during startup");
}
void Connection::StartupHandler::on_result_response(Message* response) {
ResultResponse* result = static_cast<ResultResponse*>(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::Request::Request(Connection* connection, ResponseCallback* response_callback)
: connection(connection)
, stream(0)
, response_callback_(response_callback)
, timer_(nullptr)
, state_(REQUEST_STATE_NEW) { }
void Connection::Request::on_set(Message* response) {
switch(response->opcode) {
case CQL_OPCODE_RESULT:
on_result_response(response);
break;
}
response_callback_->on_set(response);
}
void Connection::Request::on_error(CassError code, const std::string& message) {
response_callback_->on_error(code, message);
connection->stream_manager_.release_stream(stream);
}
void Connection::Request::on_timeout() {
response_callback_->on_timeout();
}
void Connection::Request::change_state(Connection::Request::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) {
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;
cleanup();
} 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;
cleanup();
break;
case REQUEST_STATE_READ_BEFORE_WRITE:
assert(next_state == REQUEST_STATE_DONE && "Invalid request state after read before write");
state_ = next_state;
cleanup();
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;
cleanup();
break;
case REQUEST_STATE_DONE:
assert(false && "Invalid request state after done");
break;
default:
assert(false && "Invalid request state");
break;
}
}
void Connection::Request::cleanup() {
connection->pending_requests_.remove(this);
delete this;
}
void Connection::Request::on_result_response(Message* response) {
ResultResponse* result = static_cast<ResultResponse*>(response->body.get());
switch(result->kind) {
case CASS_RESULT_KIND_SET_KEYSPACE:
connection->keyspace_.assign(result->keyspace, result->keyspace_size);
break;
}
}
void Connection::Request::on_request_timeout(Timer* timer) {
Request *request = static_cast<Request*>(timer->data());
request->connection->logger_->info("Request timed out to '%s'",
request->connection->host_string_.c_str());
request->timer_ = nullptr;
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, SSLSession* ssl_session, const Host& host, Logger* logger, const Config& config, const std::string& keyspace)
: state_(CLIENT_STATE_NEW)
, is_defunct_(false)
, timed_out_request_count_(0)
, loop_(loop)
, incoming_(new Message())
, host_(host)
, host_string_(host.address.to_string())
, ssl_(ssl_session)
, ssl_handshake_done_(false)
, version_("3.0.0")
, logger_(logger)
, config_(config)
, keyspace_(keyspace)
, connect_timer_(nullptr) {
socket_.data = this;
uv_tcp_init(loop_, &socket_);
if (ssl_) {
ssl_->init();
ssl_->handshake(true);
}
}
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) {
std::unique_ptr<Request> request(new Request(this, response_callback));
Message* message = response_callback->request();
int8_t stream = stream_manager_.acquire_stream(request.get());
if(request->stream < 0) {
return false;
}
request->stream = stream;
message->stream = stream;
char* buf_data;
size_t buf_length;
if (!message->prepare(&buf_data, buf_length)) {
request->on_error(CASS_ERROR_LIB_MESSAGE_PREPARE, "Unable to build request");
return true;
}
logger_->debug("Sending message type %s with %d, size %zd",
opcode_to_string(message->opcode).c_str(), message->stream, buf_length);
pending_requests_.add_to_back(request.get());
request->change_state(Request::REQUEST_STATE_WRITING);
write(uv_buf_init(buf_data, buf_length), 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(uv_buf_t buf, Connection::Request* request) {
Writer::Bufs* bufs = new Writer::Bufs({ buf });
Writer::write(reinterpret_cast<uv_stream_t*>(&socket_), 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 = incoming_->consume(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 (incoming_->body_ready) {
std::unique_ptr<Message> response(std::move(incoming_));
incoming_.reset(new Message());
logger_->debug("Consumed message type %s with stream %d, input %zd, remaining %d on '%s'",
opcode_to_string(response->opcode).c_str(), response->stream,
size, remaining, host_string_.c_str());
if (response->stream < 0) {
// TODO(mstump) system events
assert(false);
} else {
Request* request = nullptr;
if(stream_manager_.get_item(response->stream, request)) {
switch(request->state()) {
case Request::REQUEST_STATE_READING:
request->on_set(response.get());
request->change_state(Request::REQUEST_STATE_DONE);
break;
case Request::REQUEST_STATE_WRITING:
request->on_set(response.get());
request->change_state(Request::REQUEST_STATE_READ_BEFORE_WRITE);
break;
case Request::REQUEST_STATE_WRITE_TIMEOUT:
request->change_state(Request::REQUEST_STATE_READ_BEFORE_WRITE);
break;
case Request::REQUEST_STATE_READ_TIMEOUT:
request->change_state(Request::REQUEST_STATE_DONE);
break;
case Request::REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ:
request->change_state(Request::REQUEST_STATE_DONE);
break;
default:
assert(false && "Invalid request state after receiving response");
break;
}
} 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_ = nullptr;
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_ = nullptr;
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());
connection->state_ = CLIENT_STATE_CLOSED;
connection->event_received();
while(!connection->pending_requests_.is_empty()) {
Request* request = connection->pending_requests_.front();
if(request->state() == Request::REQUEST_STATE_WRITING
|| request->state() == Request::REQUEST_STATE_READING) {
request->on_timeout();
request->stop_timer();
}
connection->pending_requests_.remove(request);
delete request;
}
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;
}
if (connection->ssl_) {
char* read_input = buf.base;
size_t read_input_size = nread;
for (;;) {
size_t read_size = 0;
char* read_output = nullptr;
size_t read_output_size = 0;
char* write_output = nullptr;
size_t write_output_size = 0;
// TODO(mstump) error handling for SSL decryption
std::string error;
connection->ssl_->read_write(read_input,
read_input_size,
read_size,
&read_output,
read_output_size,
nullptr,
0,
&write_output,
write_output_size,
&error);
if (read_output && read_output_size) {
// TODO(mstump) error handling
connection->consume(read_output, read_output_size);
delete read_output;
}
if (write_output && write_output_size) {
Request* request = new Request(connection, nullptr);
connection->write(uv_buf_init(write_output, write_output_size), request);
// delete of write_output will be handled by on_write
}
if (read_size < read_input_size) {
read_input += read_size;
read_input_size -= read_size;
} else {
break;
}
if (!connection->ssl_handshake_done_) {
if (connection->ssl_->handshake_done()) {
connection->state_ = CLIENT_STATE_HANDSHAKE;
connection->event_received();
}
}
}
} else {
connection->consume(buf.base, nread);
}
free_buffer(buf);
}
void Connection::on_write(Writer* writer) {
Request* request = static_cast<Request*>(writer->data());
Connection* connection = request->connection;
switch(request->state()) {
case Request::REQUEST_STATE_WRITING:
if(writer->status() == Writer::SUCCESS) {
request->change_state(Request::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(Request::REQUEST_STATE_DONE);
}
break;
case Request::REQUEST_STATE_WRITE_TIMEOUT:
request->change_state(Request::REQUEST_STATE_WRITE_TIMEOUT_BEFORE_READ);
break;
case Request::REQUEST_STATE_READ_BEFORE_WRITE:
request->change_state(Request::REQUEST_STATE_DONE);
break;
default:
assert(false && "Invalid request state after write finished");
break;
}
}
void Connection::ssl_handshake() {
if (ssl_) {
// calling read on a handshaked initiated ssl_ pipe
// will gives us the first message to send to the server
on_read(
reinterpret_cast<uv_stream_t*>(&socket_),
0,
alloc_buffer(0));
} else {
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(Message* response) {
SupportedResponse* supported
= static_cast<SupportedResponse*>(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 Message(CQL_OPCODE_OPTIONS)));
}
void Connection::send_startup() {
Message* message = new Message(CQL_OPCODE_STARTUP);
StartupRequest* startup = static_cast<StartupRequest*>(message->body.get());
startup->version = version_;
execute(new StartupHandler(this, message));
}
void Connection::send_use_keyspace() {
Message* message = new Message(CQL_OPCODE_QUERY);
QueryRequest* query = static_cast<QueryRequest*>(message->body.get());
query->statement("use \"" + keyspace_ + "\"");
query->consistency(CASS_CONSISTENCY_ONE);
execute(new StartupHandler(this, message));
}
} // namespace cass