fix(nescope): end nescope when the hub stays gone after its socket closes

The input source now removes itself on EOF or reset and raises a flag; the
main loop exits 2 s later unless a signal already ended it, so a hub crash
mid-run still ends the compositor (and the box via CompositorDied) instead of
holding a GPU with no input. Also corrects the comment that promised a
reconnect that does not exist.
This commit is contained in:
Wanjohi
2026-10-18 15:46:14 +03:00
parent 6c2ae15941
commit 1bbf15fdca
2 changed files with 39 additions and 1 deletions
+15
View File
@@ -1,12 +1,16 @@
use std::io::{self, Read};
use std::os::unix::io::{AsFd, BorrowedFd};
use std::os::unix::net::UnixStream;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use calloop::{EventSource, Interest, Mode, Poll, PostAction, Readiness, Token, TokenFactory};
pub struct InputIpcSource {
stream: UnixStream,
buf: Vec<u8>,
/// Set once the hub has closed its end, for the main loop to act on.
closed: Arc<AtomicBool>,
}
impl InputIpcSource {
@@ -16,9 +20,17 @@ impl InputIpcSource {
Ok(Self {
stream,
buf: Vec::new(),
closed: Arc::new(AtomicBool::new(false)),
})
}
/// True once the hub has closed the socket. The main loop gives that a
/// grace period and then ends nescope: a compositor with no input holds
/// the GPU for nothing.
pub fn closed_flag(&self) -> Arc<AtomicBool> {
self.closed.clone()
}
pub fn try_clone(&self) -> io::Result<UnixStream> {
self.stream.try_clone()
}
@@ -34,6 +46,7 @@ impl InputIpcSource {
Ok(n) => self.buf.extend_from_slice(&tmp[..n]),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => return Ok(false),
Err(e) if e.kind() == io::ErrorKind::Interrupted => {}
Err(e) if e.kind() == io::ErrorKind::ConnectionReset => return Ok(true),
Err(e) => return Err(e),
}
}
@@ -81,6 +94,7 @@ impl EventSource for InputIpcSource {
}
if closed {
self.closed.store(true, Ordering::Relaxed);
tracing::warn!("input IPC socket closed by the hub; no more input from it");
return Ok(PostAction::Remove);
}
@@ -112,6 +126,7 @@ mod tests {
InputIpcSource {
stream: a,
buf: Vec::new(),
closed: Arc::new(AtomicBool::new(false)),
},
b,
)
+24 -1
View File
@@ -389,11 +389,15 @@ fn main() {
// ── Input IPC source ─────────────────────────────────────────────────
// Connect to the neshub input socket and feed events into the
// compositor seat. Reconnection is handled in the idle callback.
// compositor seat. There is no reconnection: when the hub closes the socket
// the source removes itself, and the loop below ends nescope after a short
// grace (a signal that arrives first ends it sooner).
let ipc_path = args.input_ipc.clone();
let mut hub_closed_flag: Option<std::sync::Arc<std::sync::atomic::AtomicBool>> = None;
match input_ipc::InputIpcSource::connect(&ipc_path) {
Ok(source) => {
tracing::info!("Connected to input IPC socket: {ipc_path}");
hub_closed_flag = Some(source.closed_flag());
match source.try_clone() {
Ok(write_stream) => {
state.ipc_write = Some(write_stream);
@@ -476,6 +480,7 @@ fn main() {
// never arrives there is nothing to wait for and no game to run.
let startup = std::time::Instant::now();
let mut xwayland_timed_out = false;
let mut hub_closed_at: Option<std::time::Instant> = None;
event_loop
.run(Some(Duration::from_secs(1)), &mut data, move |data| {
@@ -578,6 +583,24 @@ fn main() {
}
}
// ── Hub gone ──────────────────────────────────────────────
// neshub closing the input socket mid-run leaves a compositor
// nobody can drive. The grace lets a shutdown that is already
// under way (SIGTERM stops the loop itself) finish its own way;
// otherwise nescope ends, and nesinit ends the box on seeing it.
if hub_closed_flag
.as_ref()
.is_some_and(|f| f.load(std::sync::atomic::Ordering::Relaxed))
{
let since = hub_closed_at.get_or_insert_with(std::time::Instant::now);
if since.elapsed() > Duration::from_secs(2) {
tracing::error!("input hub gone for 2 s with no shutdown — exiting");
kill_all_children();
data.loop_signal.stop();
return;
}
}
// ── Poll primary process ──────────────────────────────────
// The launcher (e.g. Steam's shell wrapper) may exit quickly
// while the real game client stays alive as a reparented