From 9b460f179070d1e2a22e2a2c9a8e2f88a0ab1968 Mon Sep 17 00:00:00 2001 From: TuringLambdaAI Date: Fri, 9 Oct 2026 21:19:10 +0800 Subject: [PATCH] linux: terminate on SIGTERM/SIGINT in the host scaffold and example MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Linux system adapter has shipped InstallShutdownHook since #128, but nothing outside the integration probe installed it — and the GTK shutdown signal never fires on a termination signal. A TERM therefore broke the embedded Racket backend while the GTK process kept running: Fulcrum 0.4.1 kept a visible window whose searches silently did nothing, and only SIGKILL removed it. Install the hook in the host scaffold and the Taskboard example, running the same orderly shutdown as the GTK shutdown signal; the watcher thread hard-exits with EXIT_SUCCESS once it returns. --- CHANGELOG.md | 7 +++++++ examples/taskboard/linux/src/main.cpp | 11 +++++++++++ platform/linux/host/src/main.cpp | 16 ++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6dff9efb..468632b1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ ## Unreleased +- Linux hosts now terminate on SIGTERM/SIGINT. The host scaffold and the + Taskboard example install Rivet's shutdown hook so a termination signal + runs the same orderly backend shutdown as the GTK shutdown signal and + then exits; previously the embedded backend died on the signal while the + GTK process stayed up, leaving a window that could not search or run + anything (observed on Fulcrum 0.4.1: `kill` left a half-dead app that + only SIGKILL would remove). - Add a supported WinUI 3-to-`HWND` bridge. Generated Windows hosts expose `WindowHandle()` through the first-party system adapter, centralizing the Windows App SDK `IWindowNative` ABI and removing the need for title-based diff --git a/examples/taskboard/linux/src/main.cpp b/examples/taskboard/linux/src/main.cpp index b4562792..1f7b1f77 100644 --- a/examples/taskboard/linux/src/main.cpp +++ b/examples/taskboard/linux/src/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -527,12 +528,22 @@ void on_shutdown(GApplication*, gpointer) { if (g_state.backend) g_state.backend->stop(); } +// SIGTERM/SIGINT bypass the GTK main loop; run the same orderly shutdown +// the GTK shutdown signal would have run, then the hook hard-exits. +void perform_orderly_shutdown() { on_shutdown(nullptr, nullptr); } + } // namespace int main(int argc, char** argv) { auto* app = gtk_application_new("dev.rivet.taskboard", G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr); g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), nullptr); + try { + rivet::system::InstallShutdownHook(perform_orderly_shutdown); + } catch (std::exception const& error) { + std::fputs((std::string{"shutdown hook unavailable: "} + error.what() + "\n").c_str(), + stderr); + } int const status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status; diff --git a/platform/linux/host/src/main.cpp b/platform/linux/host/src/main.cpp index 9359d507..18bde105 100644 --- a/platform/linux/host/src/main.cpp +++ b/platform/linux/host/src/main.cpp @@ -6,7 +6,9 @@ #include #include +#include #include +#include #include #include #include @@ -14,6 +16,7 @@ #include #include "GeneratedBackend.hpp" +#include "system_services.hpp" #include "theme.hpp" namespace { @@ -264,6 +267,12 @@ void on_shutdown(GApplication*, gpointer) { } } +// SIGTERM/SIGINT bypass the GTK main loop, so the shutdown signal handler +// alone would leave the window alive over a dead backend. The watcher +// thread runs this same cleanup and then hard-exits; keep it synchronous +// and allocation-light. +void perform_orderly_shutdown() { on_shutdown(nullptr, nullptr); } + } // namespace int main(int argc, char** argv) { @@ -271,6 +280,13 @@ int main(int argc, char** argv) { G_APPLICATION_DEFAULT_FLAGS); g_signal_connect(app, "activate", G_CALLBACK(on_activate), nullptr); g_signal_connect(app, "shutdown", G_CALLBACK(on_shutdown), nullptr); + try { + rivet::system::InstallShutdownHook(perform_orderly_shutdown); + } catch (std::exception const& error) { + std::fputs((std::string{"shutdown hook unavailable: "} + error.what() + + "\n").c_str(), + stderr); + } int const status = g_application_run(G_APPLICATION(app), argc, argv); g_object_unref(app); return status;