Merge pull request #2116 from kacpherek/fix/2095-signal-save
🤖 Agent: Read against the vanilla 26.3 sources (Save the world on SIGTERM, Ctrl+C and SIGHUP) and proven in contrib-gate-21's union of 72 kacpherek PRs: 95 parity harnesses on playhorizon and the Mac mini, clippy, 1534 tests, lan_e2e 23 checks in-frame and with --server-thread. Thanks @kacpherek!
This commit is contained in:
@@ -8772,6 +8772,10 @@ impl Game<'_> {
|
||||
}
|
||||
self.lan_close(false);
|
||||
self.save_game()?;
|
||||
if let Some(mut remote) = self.remote.take() {
|
||||
// Minecraft.disconnect closes the connection, so the host sees a clean leave.
|
||||
remote.session.disconnect();
|
||||
}
|
||||
self.saved_for_exit = true;
|
||||
Ok(())
|
||||
}
|
||||
@@ -12773,6 +12777,16 @@ impl ApplicationHandler for Game<'_> {
|
||||
}
|
||||
}
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if horizon_threads::stop_requests() > 0
|
||||
&& self.pending_save.as_ref().is_none_or(|save| !save.quit)
|
||||
{
|
||||
// SIGTERM, SIGINT or SIGHUP: the shutdown hook's server halt, as a window close.
|
||||
if let Err(error) = self.begin_closing_save(true) {
|
||||
self.error = Some(error);
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if self.pending_save.is_some() {
|
||||
match self.poll_closing_save() {
|
||||
Ok(true) => event_loop.exit(),
|
||||
@@ -14263,6 +14277,11 @@ impl Loading {
|
||||
}
|
||||
}
|
||||
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
|
||||
if horizon_threads::stop_requests() > 0 {
|
||||
// No world is open yet: nothing to save.
|
||||
event_loop.exit();
|
||||
return;
|
||||
}
|
||||
if let Err(error) = self.poll_menu_resources() {
|
||||
self.error = Some(error);
|
||||
event_loop.exit();
|
||||
@@ -14540,6 +14559,9 @@ pub fn run() -> Result<()> {
|
||||
{
|
||||
horizon_client::heartbeat::start();
|
||||
}
|
||||
if options.execution_role.is_client() {
|
||||
horizon_threads::catch_stop_signals();
|
||||
}
|
||||
let started = Instant::now();
|
||||
horizon_client::spans::enable(options.spikes.is_some());
|
||||
let vanilla_root = options.resources.clone();
|
||||
|
||||
@@ -163,6 +163,38 @@ fn idle() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// How many stop requests (Ctrl-C, SIGTERM, SIGHUP) arrived since `catch_stop_signals`.
|
||||
pub fn stop_requests() -> u32 {
|
||||
STOP_REQUESTS.load(std::sync::atomic::Ordering::SeqCst)
|
||||
}
|
||||
|
||||
static STOP_REQUESTS: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0);
|
||||
|
||||
/// Count stop requests from outside instead of dying on them: vanilla's "Client Shutdown Thread"
|
||||
/// (`Main.java`) halts the integrated server when the JVM shuts down, and the JVM does that on
|
||||
/// these signals, so a killed game still saves. The caller's event loop turns the first request
|
||||
/// into a normal quit; a second one (the save is stuck) ends the process at once.
|
||||
#[cfg(any(target_vendor = "apple", target_os = "linux", target_os = "android"))]
|
||||
pub fn catch_stop_signals() {
|
||||
extern "C" fn handle(_: libc::c_int) {
|
||||
if STOP_REQUESTS.fetch_add(1, Ordering::SeqCst) >= 1 {
|
||||
// SAFETY: _exit is async-signal-safe; 130 is the shell's code for a Ctrl-C death.
|
||||
unsafe { libc::_exit(130) }
|
||||
}
|
||||
}
|
||||
let handler = handle as extern "C" fn(libc::c_int);
|
||||
for signal in [libc::SIGINT, libc::SIGTERM, libc::SIGHUP] {
|
||||
// SAFETY: the handler only touches an atomic and calls _exit, both async-signal-safe.
|
||||
unsafe {
|
||||
libc::signal(signal, handler as libc::sighandler_t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Windows and the other systems keep their default: a closed window is a `CloseRequested`.
|
||||
#[cfg(not(any(target_vendor = "apple", target_os = "linux", target_os = "android")))]
|
||||
pub fn catch_stop_signals() {}
|
||||
|
||||
/// Give back to the OS the memory the allocator holds free, after something
|
||||
/// large was dropped: glibc keeps freed pages in the arena of the thread that
|
||||
/// allocated them, and macOS in its zones until memory runs short. Nothing
|
||||
|
||||
Reference in New Issue
Block a user