/* * Copyright (C) 2014 ScyllaDB */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #include "server.hh" #include "handler.hh" #include "core/future-util.hh" #include "core/circular_buffer.hh" #include #include "net/byteorder.hh" #include "core/scattered_message.hh" #include "log.hh" #include #include #include #include #include #include #include #include #include #include #include #include #include #include static logging::logger logger("thrift"); using namespace apache::thrift; using namespace apache::thrift::transport; using namespace apache::thrift::protocol; using namespace apache::thrift::async; using namespace org::apache::cassandra; class thrift_stats { seastar::metrics::metric_groups _metrics; public: thrift_stats(thrift_server& server); }; thrift_server::thrift_server(distributed& db, distributed& qp) : _stats(new thrift_stats(*this)) , _handler_factory(create_handler_factory(db, qp).release()) , _protocol_factory(new TBinaryProtocolFactoryT()) , _processor_factory(new CassandraAsyncProcessorFactory(_handler_factory)) { } thrift_server::~thrift_server() { } // FIXME: this is here because we must have a stop function. But we should actually // do something useful - or be sure it is not needed future<> thrift_server::stop() { return make_ready_future<>(); } struct handler_deleter { CassandraCobSvIfFactory* hf; void operator()(CassandraCobSvIf* h) const { hf->releaseHandler(h); } }; class thrift_server::connection { // thrift uses a shared_ptr to refer to the transport (= connection), // while we do not, so we can't have connection inherit from TTransport. struct fake_transport : TTransport { fake_transport(connection* c) : conn(c) {} connection* conn; }; thrift_server& _server; connected_socket _fd; input_stream _read_buf; output_stream _write_buf; temporary_buffer _in_tmp; boost::shared_ptr _transport = boost::make_shared(this); boost::shared_ptr _input = boost::make_shared(); boost::shared_ptr _output = boost::make_shared(); boost::shared_ptr _in_proto = _server._protocol_factory->getProtocol(_input); boost::shared_ptr _out_proto = _server._protocol_factory->getProtocol(_output); boost::shared_ptr _processor; promise<> _processor_promise; public: connection(thrift_server& server, connected_socket&& fd, socket_address addr) : _server(server), _fd(std::move(fd)), _read_buf(_fd.input()) , _write_buf(_fd.output()) , _processor(_server._processor_factory->getProcessor(get_conn_info())) { ++_server._total_connections; ++_server._current_connections; } ~connection() { --_server._current_connections; } TConnectionInfo get_conn_info() { return { _in_proto, _out_proto, _transport }; } future<> process() { return do_until([this] { return _read_buf.eof(); }, [this] { return process_one_request(); }); } future<> process_one_request() { _input->resetBuffer(); _output->resetBuffer(); return read().then([this] { ++_server._requests_served; auto ret = _processor_promise.get_future(); // adapt from "continuation object style" to future/promise auto complete = [this] (bool success) mutable { // FIXME: look at success? write().forward_to(std::move(_processor_promise)); _processor_promise = promise<>(); }; _processor->process(complete, _in_proto, _out_proto); return ret; }); } future<> read() { return _read_buf.read_exactly(4).then([this] (temporary_buffer size_buf) { if (size_buf.size() != 4) { return make_ready_future<>(); } union { uint32_t n; char b[4]; } data; std::copy_n(size_buf.get(), 4, data.b); auto n = ntohl(data.n); return _read_buf.read_exactly(n).then([this, n] (temporary_buffer buf) { if (buf.size() != n) { // FIXME: exception perhaps? return; } _in_tmp = std::move(buf); // keep ownership of the data auto b = reinterpret_cast(_in_tmp.get_write()); _input->resetBuffer(b, _in_tmp.size()); }); }); } future<> write() { uint8_t* data; uint32_t len; _output->getBuffer(&data, &len); net::packed plen = { net::hton(len) }; return _write_buf.write(reinterpret_cast(&plen), 4).then([this, data, len] { // FIXME: zero-copy return _write_buf.write(reinterpret_cast(data), len); }).then([this] { return _write_buf.flush(); }); } }; future<> thrift_server::listen(ipv4_addr addr, bool keepalive) { listen_options lo; lo.reuse_address = true; _listeners.push_back(engine().listen(make_ipv4_address(addr), lo)); do_accepts(_listeners.size() - 1, keepalive); return make_ready_future<>(); } void thrift_server::do_accepts(int which, bool keepalive) { _listeners[which].accept().then([this, which, keepalive] (connected_socket fd, socket_address addr) mutable { fd.set_nodelay(true); fd.set_keepalive(keepalive); auto conn = new connection(*this, std::move(fd), addr); conn->process().then_wrapped([this, conn] (future<> f) { delete conn; try { f.get(); } catch (std::exception& ex) { logger.debug("request error {}", ex.what()); } }); do_accepts(which, keepalive); }).then_wrapped([] (future<> f) { try { f.get(); } catch (std::exception& ex) { std::cout << "accept failed: " << ex.what() << "\n"; } }); } uint64_t thrift_server::total_connections() const { return _total_connections; } uint64_t thrift_server::current_connections() const { return _current_connections; } uint64_t thrift_server::requests_served() const { return _requests_served; } thrift_stats::thrift_stats(thrift_server& server) { namespace sm = seastar::metrics; _metrics.add_group("thrift", { sm::make_derive("thrift-connections", [&server] { return server.total_connections(); }, sm::description("Rate of creation of new Thrift connections.")), sm::make_gauge("current_connections", [&server] { return server.current_connections(); }, sm::description("Holds a current number of opened Thrift connections.")), sm::make_derive("served", [&server] { return server.requests_served(); }, sm::description("Rate of serving Thrift requests.")), }); }