add signal handlers; closes #564

This commit is contained in:
Martin Michelsen
2024-10-21 22:38:58 -07:00
parent 086b2d411a
commit a9a15600b2
6 changed files with 114 additions and 1 deletions
+1
View File
@@ -123,6 +123,7 @@ set(SOURCES
src/Server.cc
src/ServerShell.cc
src/ServerState.cc
src/SignalWatcher.cc
src/StaticGameData.cc
src/TeamIndex.cc
src/Text.cc
+4
View File
@@ -148,6 +148,10 @@ There are currently no precompiled releases for Linux. To run newserv on Linux,
After building newserv, edit system/config.example.json as needed **and rename it to system/config.json** (note that this step is not necessary for the precompiled releases!), set up [client patch directories](#client-patch-directories) if you're planning to play Blue Burst, then run `./newserv` in newserv's directory.
The server has an interactive shell which can be used to make changes, such as managing user accounts, updating the server's configuration, managing Episode 3 tournaments, and more. Type `help` and press Enter to see all the commands.
On Linux and macOS, the server also responds to SIGUSR1 and SIGUSR2. SIGUSR1 does the equivalent of the shell's `reload config` command, which reloads config.json but not any dependent files (so quests, Episode 3 maps, etc. will not be reloaded). SIGUSR2 does the equivalent of the shell's `reload all` command, which reloads everything.
To use newserv in other ways (e.g. for translating data), see the end of this document.
## Client patch directories
+7
View File
@@ -46,6 +46,7 @@
#include "Server.hh"
#include "ServerShell.hh"
#include "ServerState.hh"
#include "SignalWatcher.hh"
#include "StaticGameData.hh"
#include "Text.hh"
#include "TextIndex.hh"
@@ -2727,6 +2728,7 @@ Action a_run_server_replay_log(
shared_ptr<ServerShell> shell;
shared_ptr<ReplaySession> replay_session;
shared_ptr<SignalWatcher> signal_watcher;
if (is_replay) {
config_log.info("Starting proxy server");
state->proxy_server = make_shared<ProxyServer>(base, state);
@@ -2842,6 +2844,11 @@ Action a_run_server_replay_log(
state->http_server->listen(netloc.first, netloc.second);
}
}
#ifndef PHOSG_WINDOWS
config_log.info("Enabling signal watcher");
signal_watcher = make_shared<SignalWatcher>(state);
#endif
}
if (!state->username.empty()) {
+11 -1
View File
@@ -232,6 +232,7 @@ CommandDefinition c_reload(
teams - reindex all BB teams\n\
text-index - reload in-game text\n\
word-select - regenerate the Word Select translation table\n\
all - do all of the above\n\
Reloading will not affect items that are in use; for example, if an Episode\n\
3 battle is in progress, it will continue to use the previous map and card\n\
definitions. Similarly, BB clients are not forced to disconnect or reload\n\
@@ -242,7 +243,16 @@ CommandDefinition c_reload(
+[](CommandArgs& args) {
auto types = phosg::split(args.args, ' ');
for (const auto& type : types) {
if (type == "bb-keys") {
if (type == "all") {
args.s->forward_to_event_thread([s = args.s]() {
try {
s->load_all();
} catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what());
fprintf(stderr, "Some configuration may have been reloaded. Fix the underlying issue and try again.\n");
}
});
} else if (type == "bb-keys") {
args.s->load_bb_private_keys(true);
} else if (type == "accounts") {
args.s->load_accounts(true);
+63
View File
@@ -0,0 +1,63 @@
#include "SignalWatcher.hh"
#include <event2/event.h>
#include <stdio.h>
#include <string.h>
#include <phosg/Random.hh>
#include <phosg/Strings.hh>
#include "ReceiveCommands.hh"
#include "SendCommands.hh"
#include "ServerState.hh"
#include "StaticGameData.hh"
using namespace std;
SignalWatcher::SignalWatcher(shared_ptr<ServerState> state)
: log("[SignalWatcher] "),
state(state),
sigusr1_event(evsignal_new(this->state->base.get(), SIGUSR1, &SignalWatcher::dispatch_on_signal, this), event_free),
sigusr2_event(evsignal_new(this->state->base.get(), SIGUSR2, &SignalWatcher::dispatch_on_signal, this), event_free) {
event_add(this->sigusr1_event.get(), nullptr);
event_add(this->sigusr2_event.get(), nullptr);
}
void SignalWatcher::dispatch_on_signal(evutil_socket_t signal, short what, void* ctx) {
if (what != EV_SIGNAL) {
throw logic_error("dispatch_on_signal called for non-signal event");
}
reinterpret_cast<SignalWatcher*>(ctx)->on_signal(signal);
}
void SignalWatcher::on_signal(int signum) {
switch (signum) {
case SIGUSR1:
this->log.info("Received SIGUSR1; reloading config.json");
this->state->forward_to_event_thread([s = this->state]() {
try {
s->load_config_early();
s->load_config_late();
fprintf(stderr, "Configuration update complete\n");
} catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what());
fprintf(stderr, "Some configuration may have been reloaded. Fix the underlying issue and try again.\n");
}
});
break;
case SIGUSR2:
this->log.info("Received SIGUSR2; reloading config.json and all dependencies");
this->state->forward_to_event_thread([s = this->state]() {
try {
s->load_all();
fprintf(stderr, "Configuration update complete\n");
} catch (const exception& e) {
fprintf(stderr, "FAILED: %s\n", e.what());
fprintf(stderr, "Some configuration may have been reloaded. Fix the underlying issue and try again.\n");
}
});
break;
default:
this->log.warning("Unknown signal received: %d", signum);
}
}
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include <memory>
#include <string>
#include <thread>
#include <event2/event.h>
#include "ServerState.hh"
class SignalWatcher : public std::enable_shared_from_this<SignalWatcher> {
public:
explicit SignalWatcher(std::shared_ptr<ServerState> state);
SignalWatcher(const SignalWatcher&) = delete;
SignalWatcher(SignalWatcher&&) = delete;
SignalWatcher& operator=(const SignalWatcher&) = delete;
SignalWatcher& operator=(SignalWatcher&&) = delete;
~SignalWatcher() = default;
protected:
phosg::PrefixedLogger log;
std::shared_ptr<ServerState> state;
std::unique_ptr<struct event, void (*)(struct event*)> sigusr1_event;
std::unique_ptr<struct event, void (*)(struct event*)> sigusr2_event;
static void dispatch_on_signal(evutil_socket_t, short, void* ctx);
void on_signal(int signum);
};