From 636bdb8ebddd0a64b07d232838bd4a2358264510 Mon Sep 17 00:00:00 2001 From: Nathan Herald Date: Wed, 4 Nov 2026 01:52:45 +0100 Subject: [PATCH] Give the no-PID keyed test a socket that refuses whoever holds it a_record_naming_no_pid_is_probed_but_never_turned_into_a_loss dropped its UnixListener and expected the next probe to be refused. A dropped listener can still accept: pty-core's unit tests run on parallel threads, some spawn processes, and a child that another thread is spawning holds a copy of every descriptor from its fork until its exec closes them. A probe in that window connects, and the observer correctly reads the session as running. That is the failure seen once in CI: Found(Running, Unknown) where Indeterminate(SocketProbe) was expected. registry_support::stale_socket already gives the integration tests a socket that was bound and never listened on for this reason. Replace the dropped listener with one made the same way. The observer is unchanged. --- crates/pty-core/src/registry/keyed.rs | 38 +++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/crates/pty-core/src/registry/keyed.rs b/crates/pty-core/src/registry/keyed.rs index e8a107e8..f472d72c 100644 --- a/crates/pty-core/src/registry/keyed.rs +++ b/crates/pty-core/src/registry/keyed.rs @@ -1309,6 +1309,41 @@ mod tests { } } + /// A socket that refuses every connect, as `registry_support::stale_socket` + /// makes for the integration tests: bound and never listened on. A dropped + /// `UnixListener` can still accept, because a child that another test + /// thread is spawning holds a copy of it from its fork until its exec. + fn stale_socket(path: &Path) { + let bytes = std::os::unix::ffi::OsStrExt::as_bytes(path.as_os_str()); + // SAFETY: plain socket calls on a descriptor this function owns and + // closes; `addr` is zeroed and `bytes` fits with its terminating NUL. + let (rc, err) = unsafe { + let fd = libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0); + assert!(fd >= 0, "socket: {}", io::Error::last_os_error()); + let mut addr: libc::sockaddr_un = std::mem::zeroed(); + addr.sun_family = libc::AF_UNIX as libc::sa_family_t; + assert!( + bytes.len() < addr.sun_path.len(), + "{} is too long", + path.display() + ); + for (dst, src) in addr.sun_path.iter_mut().zip(bytes) { + *dst = *src as libc::c_char; + } + let rc = libc::bind( + fd, + (&raw const addr).cast::(), + std::mem::size_of::() as libc::socklen_t, + ); + let err = io::Error::last_os_error(); + libc::close(fd); + (rc, err) + }; + assert_eq!(rc, 0, "bind {}: {err}", path.display()); + let refused = std::os::unix::net::UnixStream::connect(path).unwrap_err(); + assert_eq!(refused.kind(), io::ErrorKind::ConnectionRefused); + } + fn listed(root: &Root) -> SessionInfo { super::super::list::list_sessions_in(&root.0, &ListOptions::default()) .into_iter() @@ -1368,6 +1403,9 @@ mod tests { (SessionStatus::Running, DaemonIdentityCheck::Unknown) ); drop(listener); + let socket = socket_path_in(&root.0, "target"); + std::fs::remove_file(&socket).unwrap(); + stale_socket(&socket); // A refusing socket still lists as Running; this observer cannot // prove the record either way, so it fails closed. assert_eq!(listed(&root).status, SessionStatus::Running);