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/Server.cc
src/ServerShell.cc src/ServerShell.cc
src/ServerState.cc src/ServerState.cc
src/SignalWatcher.cc
src/StaticGameData.cc src/StaticGameData.cc
src/TeamIndex.cc src/TeamIndex.cc
src/Text.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. 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. To use newserv in other ways (e.g. for translating data), see the end of this document.
## Client patch directories ## Client patch directories
+7
View File
@@ -46,6 +46,7 @@
#include "Server.hh" #include "Server.hh"
#include "ServerShell.hh" #include "ServerShell.hh"
#include "ServerState.hh" #include "ServerState.hh"
#include "SignalWatcher.hh"
#include "StaticGameData.hh" #include "StaticGameData.hh"
#include "Text.hh" #include "Text.hh"
#include "TextIndex.hh" #include "TextIndex.hh"
@@ -2727,6 +2728,7 @@ Action a_run_server_replay_log(
shared_ptr<ServerShell> shell; shared_ptr<ServerShell> shell;
shared_ptr<ReplaySession> replay_session; shared_ptr<ReplaySession> replay_session;
shared_ptr<SignalWatcher> signal_watcher;
if (is_replay) { if (is_replay) {
config_log.info("Starting proxy server"); config_log.info("Starting proxy server");
state->proxy_server = make_shared<ProxyServer>(base, state); 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); 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()) { if (!state->username.empty()) {
+11 -1
View File
@@ -232,6 +232,7 @@ CommandDefinition c_reload(
teams - reindex all BB teams\n\ teams - reindex all BB teams\n\
text-index - reload in-game text\n\ text-index - reload in-game text\n\
word-select - regenerate the Word Select translation table\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\ 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\ 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\ definitions. Similarly, BB clients are not forced to disconnect or reload\n\
@@ -242,7 +243,16 @@ CommandDefinition c_reload(
+[](CommandArgs& args) { +[](CommandArgs& args) {
auto types = phosg::split(args.args, ' '); auto types = phosg::split(args.args, ' ');
for (const auto& type : types) { 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); args.s->load_bb_private_keys(true);
} else if (type == "accounts") { } else if (type == "accounts") {
args.s->load_accounts(true); 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);
};