// Copyright 2022 Memgraph Ltd. // // Use of this software is governed by the Business Source License // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source // License, and you may not use this file except in compliance with the Business Source License. // // As of the Change Date specified in that file, in accordance with // the Business Source License, use of this software will be governed // by the Apache License, Version 2.0, included in the file // licenses/APL.txt. #pragma once #include #include #include #include #include #include #include #include "communication/init.hpp" #include "communication/listener.hpp" #include "io/network/socket.hpp" #include "utils/logging.hpp" #include "utils/message.hpp" #include "utils/thread.hpp" namespace memgraph::communication { /** * Communication server. * * Listens for incoming connections on the server port and assigns them to the * connection listener. The listener processes the events with a thread pool * that has `num_workers` threads. It is started automatically on constructor, * and stopped at destructor. * * Current Server architecture: * incoming connection -> server -> listener -> session * * NOTE: If you use this server you **must** create communication::SSLInit` * from the `main` function before using the server! * * @tparam TSession 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 TSessionData the class with objects that will be forwarded to the * session */ template class Server final { public: using Socket = io::network::Socket; /** * Constructs and binds server to endpoint, operates on session data and * invokes workers_count workers */ Server(const io::network::Endpoint &endpoint, TSessionData *session_data, ServerContext *context, int inactivity_timeout_sec, const std::string &service_name, size_t workers_count = std::thread::hardware_concurrency()) : alive_(false), endpoint_(endpoint), listener_(session_data, context, inactivity_timeout_sec, service_name, workers_count), service_name_(service_name) {} ~Server() { MG_ASSERT(!alive_ && !thread_.joinable(), "You should call Shutdown and " "AwaitShutdown on " "communication::Server!"); } Server(const Server &) = delete; Server(Server &&) = delete; Server &operator=(const Server &) = delete; Server &operator=(Server &&) = delete; const auto &endpoint() const { MG_ASSERT(alive_, "You can't get the server endpoint when it's not running!"); return socket_.endpoint(); } /// Starts the server bool Start() { MG_ASSERT(!alive_, "The server was already started!"); alive_.store(true); if (!socket_.Bind(endpoint_)) { spdlog::error( utils::MessageWithLink("Cannot bind to socket on endpoint {}.", endpoint_, "https://memgr.ph/socket")); alive_.store(false); return false; } socket_.SetTimeout(1, 0); if (!socket_.Listen(1024)) { spdlog::error(utils::MessageWithLink("Cannot listen on socket {}", endpoint_, "https://memgr.ph/socket")); alive_.store(false); return false; } listener_.Start(); thread_ = std::thread([this]() { utils::ThreadSetName(fmt::format("{} server", service_name_)); spdlog::info("{} server is fully armed and operational", service_name_); spdlog::info("{} listening on {}", service_name_, socket_.endpoint()); while (alive_) { AcceptConnection(); } spdlog::info("{} shutting down...", service_name_); }); return true; } /// Signals the server to start shutting down void Shutdown() { // This should be as simple as possible, so that it can be called inside a // signal handler. alive_.store(false); // Shutdown the socket to return from any waiting `Accept` calls. socket_.Shutdown(); // Shutdown the listener. listener_.Shutdown(); } /// Waits for the server to be signaled to shutdown void AwaitShutdown() { if (thread_.joinable()) thread_.join(); listener_.AwaitShutdown(); } /// Returns `true` if the server was started bool IsRunning() { return alive_; } private: void AcceptConnection() { // Accept a connection from a socket. auto s = socket_.Accept(); if (!s) { // Connection is not available anymore or configuration failed. return; } spdlog::info("Accepted a {} connection from {}", service_name_, s->endpoint()); listener_.AddConnection(std::move(*s)); } std::atomic alive_; std::thread thread_; Socket socket_; io::network::Endpoint endpoint_; Listener listener_; const std::string service_name_; }; } // namespace memgraph::communication