atagen

monolith

public
Star0
Watch0

Commit 8ae66f06204a

tarball

atagen <boss@atagen.co>2026-10-19 07:54 UTC

2 files changed+60-0
main: stop the event loop on SIGINT and SIGTERM

Without a handler a logout killed the process outright, leaving the wayland socket and the X11 lock file behind for the next session to trip over. The handler uses a self-pipe rather than calloop's signalfd source, since a blocked signal mask would be inherited by every program monolith launches.
Modified file: crates/monolith/src/main.rs+2 additions
@@ -22,6 +22,7 @@ mod pacing;
2222 mod power;
2323 mod scene;
2424 mod seat;
25+mod signals;
2526 mod state;
2627 mod stats;
2728 mod toplevels;
@@ -269,6 +270,7 @@ fn run(
269270 None => nested::init(&mut event_loop, &mut state)?,
270271 }
271272 ipc::init(&event_loop, &mut state)?;
273+ signals::init(&event_loop)?;
272274 xwayland::init(&mut state);
273275 state.configure_idle();
274276 tracing::info!(socket = ?state.core.socket_name, device = state.renderer.device_name(), "monolith running");
Added file: crates/monolith/src/signals.rs+58 additions
@@ -0,0 +1,58 @@
1+//! SIGINT and SIGTERM stop the event loop like `msg quit`, so the socket and
2+//! the X11 lock file are cleaned up when a session manager logs us out.
3+
4+use crate::state::State;
5+use smithay::reexports::calloop::generic::Generic;
6+use smithay::reexports::calloop::{EventLoop, Interest, Mode, PostAction};
7+use std::io::{ErrorKind, Read};
8+use std::os::fd::IntoRawFd;
9+use std::os::unix::net::UnixStream;
10+use std::sync::atomic::{AtomicI32, Ordering};
11+
12+const STOPPING: [libc::c_int; 2] = [libc::SIGINT, libc::SIGTERM];
13+
14+static WAKE: AtomicI32 = AtomicI32::new(-1);
15+
16+extern "C" fn on_signal(sig: libc::c_int) {
17+ let byte = sig as u8;
18+ // SAFETY: write is async-signal-safe and the buffer is a live local.
19+ unsafe { libc::write(WAKE.load(Ordering::Relaxed), (&raw const byte).cast(), 1) };
20+}
21+
22+pub fn init(event_loop: &EventLoop<'static, State>) -> std::io::Result<()> {
23+ let (wake, read) = UnixStream::pair()?;
24+ read.set_nonblocking(true)?;
25+ wake.set_nonblocking(true)?;
26+ WAKE.store(wake.into_raw_fd(), Ordering::Relaxed);
27+ // SAFETY: the handler only calls write, and the set is a plain local.
28+ unsafe {
29+ let mut set: libc::sigset_t = std::mem::zeroed();
30+ libc::sigemptyset(&mut set);
31+ for sig in STOPPING {
32+ libc::signal(sig, on_signal as *const () as libc::sighandler_t);
33+ libc::sigaddset(&mut set, sig);
34+ }
35+ libc::pthread_sigmask(libc::SIG_UNBLOCK, &set, std::ptr::null_mut());
36+ }
37+ event_loop
38+ .handle()
39+ .insert_source(
40+ Generic::new(read, Interest::READ, Mode::Level),
41+ |_, read, state| {
42+ let mut buf = [0u8; 16];
43+ loop {
44+ match (&**read).read(&mut buf) {
45+ Ok(0) => return Ok(PostAction::Remove),
46+ Ok(_) => {}
47+ Err(e) if e.kind() == ErrorKind::WouldBlock => break,
48+ Err(e) => return Err(e),
49+ }
50+ }
51+ tracing::info!("stop signal received");
52+ state.core.signal.stop();
53+ Ok(PostAction::Continue)
54+ },
55+ )
56+ .map_err(|e| std::io::Error::other(e.to_string()))?;
57+ Ok(())
58+}