Handle SIGTERM/SIGINT with a sigaction self-pipe instead of a signalfd, and clear the signal mask of spawned children
The signalfd-based source (calloop's `signals` feature) needs SIGTERM and SIGINT blocked in every thread of the process, since the kernel hands a process-directed signal to any thread that does not block it. calloop only blocks them in the thread that creates the source, so the runtime depended on `ltk::run` being entered before the app spawned any thread — a constraint apps do not honour: crustace-notifier builds its tokio runtime and its D-Bus thread first, and in that configuration a SIGTERM lands on a tokio worker and kills the process outright, skipping the clean exit (`save_state`, `mark_clean_exit`) entirely. The blocked mask had a second consequence: it is inherited across fork and exec, and Rust's std deliberately does not reset it in the child. Every process an ltk app spawns from a thread created after `ltk::run` therefore started with SIGTERM blocked. For the `gsettings monitor` child of the text-scale watcher this meant it never died on systemd's stop, so the unit's cgroup stayed populated until `TimeoutStopSec` (90 s) expired and systemd resorted to SIGKILL — visible as a stop job holding up every session shutdown. `install_signal_source` now installs a `sigaction` handler for SIGTERM and SIGINT that writes the signal number to a non-blocking `O_CLOEXEC` pipe (an atomic load and a `write`, errno preserved); the read end is a calloop `Generic` source that drains the pipe and sets `exit_requested`. A handler is process-wide, so the clean exit works no matter which thread receives the signal or when it was created, and no thread blocks anything, so children inherit a clean mask. `SA_RESTART` keeps the handler from injecting spurious EINTRs into the app's own threads; the pipe wakes the event loop regardless. `text_scale` additionally clears the child's mask in `pre_exec` before running `gsettings`, as a guard against a mask already dirty when the process was launched. The `save_state` docs drop the caveat about threads started before `run`, the ordering comment at the call site goes with the constraint it described, and the `signals` feature is dropped from calloop. `libc` becomes a direct dependency for `sigaction`, `pipe2` and `sigprocmask`. Tests: `child_starts_with_clear_signal_mask` blocks SIGTERM in the test thread, spawns a child through the helper and checks its `SigBlk` in `/proc`, with the inverse control asserting that std really does inherit the mask (the reason the helper exists); `handler_writes_signal_to_pipe` exercises the handler directly and through a real `sigaction` + `raise`, then restores `SIG_DFL`.
This commit is contained in:
+3
-4
@@ -419,10 +419,9 @@ pub trait App: 'static
|
||||
/// The file is plain on disk: never put secrets in it (passwords
|
||||
/// belong to the secure-mode `text_edit` and its wiped buffers),
|
||||
/// and keep large blobs (images, caches) elsewhere. The signal
|
||||
/// handler only covers threads started after [`run`](crate::run)
|
||||
/// was entered; a thread the app spawned earlier that receives
|
||||
/// `SIGTERM` still terminates the process — block the signal in
|
||||
/// such threads or start them from inside the app.
|
||||
/// handler is installed process-wide when [`run`](crate::run) is
|
||||
/// entered, so the clean exit holds no matter which thread receives
|
||||
/// the signal or when it was started.
|
||||
///
|
||||
/// [`restore_state`](Self::restore_state) explains when — and when
|
||||
/// not — the bytes come back.
|
||||
|
||||
Reference in New Issue
Block a user