diff --git a/CHANGELOG.md b/CHANGELOG.md index 9e71777a..e5322722 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). - Honor `linux-binary-name` in `rivet.rktd`: the Linux build stage, package staging, verification, and launch smoke now use the configured executable name instead of the hardcoded scaffold name `RivetHost` (default 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;