add signal handlers; closes #564
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user