memgraph/src/communication/server.hpp

123 lines
3.5 KiB
C++
Raw Normal View History

#pragma once
#include <atomic>
#include <iostream>
#include <memory>
#include <thread>
#include <vector>
#include <fmt/format.h>
#include <glog/logging.h>
#include "communication/worker.hpp"
#include "io/network/event_listener.hpp"
#include "utils/assert.hpp"
namespace communication {
/**
* TODO (mferencevic): document methods
*/
/**
* Communication server.
* Listens for incomming connections on the server port and assings them in a
* round-robin manner to it's workers.
*
* Current Server achitecture:
* incomming connection -> server -> worker -> session
*
* @tparam Session the server can handle different Sessions, each session
* represents a different protocol so the same network infrastructure
* can be used for handling different protocols
* @tparam OutputStream the server has to get the output stream as a template
parameter because the output stream is templated
* @tparam Socket the input/output socket that should be used
* @tparam SessionData the class with objects that will be forwarded to the session
*/
template <typename Session, typename OutputStream, typename Socket, typename SessionData>
class Server
: public io::network::EventListener<Server<Session, OutputStream, Socket, SessionData>> {
using Event = io::network::Epoll::Event;
public:
Server(Socket &&socket, SessionData &session_data)
: socket_(std::forward<Socket>(socket)),
session_data_(session_data) {
event_.data.fd = socket_;
// TODO: EPOLLET is hard to use -> figure out how should EPOLLET be used
// event.events = EPOLLIN | EPOLLET;
event_.events = EPOLLIN;
this->listener_.Add(socket_, &event_);
}
void Start(size_t n) {
std::cout << fmt::format("Starting {} workers", n) << std::endl;
workers_.reserve(n);
for (size_t i = 0; i < n; ++i) {
workers_.push_back(
std::make_unique<Worker<Session, OutputStream, Socket, SessionData>>(
session_data_));
workers_.back()->Start(alive_);
}
std::cout << "Server is fully armed and operational" << std::endl;
std::cout << fmt::format("Listening on {} at {}",
socket_.endpoint().address(),
socket_.endpoint().port())
<< std::endl;
while (alive_) {
this->WaitAndProcessEvents();
}
Fix errors with handling SIGSEGV and SIGABRT Summary: There were a couple of issues with handling the above 2 signals. 1) Calling `std::exit` from a signal handler is undefined behaviour. The only defined way for a signal handler to stop the program is calling one of: `std::_Exit`, `std::abort` or `std::quick_exit`. Neither of them will completely clean the resources, so a clean exit is not possible. Since SIGSEGV and SIGABRT happen in extraordinary circumstances that we wish to debug 99% of the time, it makes sense to generate a core dump which can be inspected by a debugger. Of the 3 termination functions, only `std::abort` will generate a core dump, so it makes sense to use that to stop the program. Also, since we are now aborting as is the default behaviour on SIGSEGV and SIGABRT, it becomes questionable why have a custom handler at all. 2) Raising an exception inside a signal handler is undefined behaviour Although the handler by itself does not raise an exception, it is possible for the logging facility to raise one. This is a real case when logging a stack trace in particular. Stack trace is generated by creating a string "<function name> <line location>". It is possible that a function name will contain '{}' somewhere inside. This is usually the case with anonymous functions. The generated string is then passed to logging, which uses the `fmt` library to fill '{}' with remaining arguments. Since only a single argument (the stack trace string) is passed for formatting, naturally the `fmt::format` throws an exception, that it is missing a format argument. We could provide an overload which takes a single string, but that defeats the purpose of `fmt::format` raising an exception in regular code if we forget to pass an argument. Another solution is to escape the whole stack trace string, so it is valid for formatting, but this only complicates the handler even further. The simplest solution is to send the stack trace to `stderr` and avoid logging altogether. Simplify Shutdown, so it can be used in a signal handler Reviewers: florijan, mferencevic, buda, mislav.bradac Reviewed By: mferencevic, buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D474
2017-06-14 22:37:23 +08:00
std::cout << "Shutting down..." << std::endl;
Fix errors with handling SIGSEGV and SIGABRT Summary: There were a couple of issues with handling the above 2 signals. 1) Calling `std::exit` from a signal handler is undefined behaviour. The only defined way for a signal handler to stop the program is calling one of: `std::_Exit`, `std::abort` or `std::quick_exit`. Neither of them will completely clean the resources, so a clean exit is not possible. Since SIGSEGV and SIGABRT happen in extraordinary circumstances that we wish to debug 99% of the time, it makes sense to generate a core dump which can be inspected by a debugger. Of the 3 termination functions, only `std::abort` will generate a core dump, so it makes sense to use that to stop the program. Also, since we are now aborting as is the default behaviour on SIGSEGV and SIGABRT, it becomes questionable why have a custom handler at all. 2) Raising an exception inside a signal handler is undefined behaviour Although the handler by itself does not raise an exception, it is possible for the logging facility to raise one. This is a real case when logging a stack trace in particular. Stack trace is generated by creating a string "<function name> <line location>". It is possible that a function name will contain '{}' somewhere inside. This is usually the case with anonymous functions. The generated string is then passed to logging, which uses the `fmt` library to fill '{}' with remaining arguments. Since only a single argument (the stack trace string) is passed for formatting, naturally the `fmt::format` throws an exception, that it is missing a format argument. We could provide an overload which takes a single string, but that defeats the purpose of `fmt::format` raising an exception in regular code if we forget to pass an argument. Another solution is to escape the whole stack trace string, so it is valid for formatting, but this only complicates the handler even further. The simplest solution is to send the stack trace to `stderr` and avoid logging altogether. Simplify Shutdown, so it can be used in a signal handler Reviewers: florijan, mferencevic, buda, mislav.bradac Reviewed By: mferencevic, buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D474
2017-06-14 22:37:23 +08:00
for (auto &worker : workers_) worker->thread_.join();
}
void Shutdown() {
Fix errors with handling SIGSEGV and SIGABRT Summary: There were a couple of issues with handling the above 2 signals. 1) Calling `std::exit` from a signal handler is undefined behaviour. The only defined way for a signal handler to stop the program is calling one of: `std::_Exit`, `std::abort` or `std::quick_exit`. Neither of them will completely clean the resources, so a clean exit is not possible. Since SIGSEGV and SIGABRT happen in extraordinary circumstances that we wish to debug 99% of the time, it makes sense to generate a core dump which can be inspected by a debugger. Of the 3 termination functions, only `std::abort` will generate a core dump, so it makes sense to use that to stop the program. Also, since we are now aborting as is the default behaviour on SIGSEGV and SIGABRT, it becomes questionable why have a custom handler at all. 2) Raising an exception inside a signal handler is undefined behaviour Although the handler by itself does not raise an exception, it is possible for the logging facility to raise one. This is a real case when logging a stack trace in particular. Stack trace is generated by creating a string "<function name> <line location>". It is possible that a function name will contain '{}' somewhere inside. This is usually the case with anonymous functions. The generated string is then passed to logging, which uses the `fmt` library to fill '{}' with remaining arguments. Since only a single argument (the stack trace string) is passed for formatting, naturally the `fmt::format` throws an exception, that it is missing a format argument. We could provide an overload which takes a single string, but that defeats the purpose of `fmt::format` raising an exception in regular code if we forget to pass an argument. Another solution is to escape the whole stack trace string, so it is valid for formatting, but this only complicates the handler even further. The simplest solution is to send the stack trace to `stderr` and avoid logging altogether. Simplify Shutdown, so it can be used in a signal handler Reviewers: florijan, mferencevic, buda, mislav.bradac Reviewed By: mferencevic, buda Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D474
2017-06-14 22:37:23 +08:00
// This should be as simple as possible, so that it can be called inside a
// signal handler.
alive_.store(false);
}
void OnConnect() {
debug_assert(idx_ < workers_.size(), "Invalid worker id.");
DLOG(INFO) << "on connect";
if (UNLIKELY(!workers_[idx_]->Accept(socket_))) return;
idx_ = idx_ == static_cast<int>(workers_.size()) - 1 ? 0 : idx_ + 1;
}
void OnWaitTimeout() {}
void OnDataEvent(Event &event) {
if (UNLIKELY(socket_ != event.data.fd)) return;
this->derived().OnConnect();
}
template <class... Args>
void OnExceptionEvent(Event &, Args &&...) {
// TODO: Do something about it
DLOG(WARNING) << "epoll exception";
}
void OnCloseEvent(Event &event) { close(event.data.fd); }
void OnErrorEvent(Event &event) { close(event.data.fd); }
private:
std::vector<typename Worker<Session, OutputStream, Socket, SessionData>::uptr> workers_;
std::atomic<bool> alive_{true};
int idx_{0};
Socket socket_;
Event event_;
SessionData &session_data_;
};
} // namespace communication