165 lines
5.0 KiB
C++
165 lines
5.0 KiB
C++
// 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 <atomic>
|
|
#include <iostream>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#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 <typename TSession, typename TSessionData>
|
|
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<bool> alive_;
|
|
std::thread thread_;
|
|
|
|
Socket socket_;
|
|
io::network::Endpoint endpoint_;
|
|
Listener<TSession, TSessionData> listener_;
|
|
|
|
const std::string service_name_;
|
|
};
|
|
|
|
} // namespace memgraph::communication
|