T04: close the pty master before reaping, so a program ignoring SIGHUP ends
CI / platforms (windows-latest) (push) Failing after 7s
CI / macos (push) Successful in 10m42s
CI / platforms (ubuntu-24.04) (push) Canceled after 0s

This commit is contained in:
ZacharyZhang-NY committed 2026-10-14 12:15:17 -04:00
1 parent fff436c8e9
commit 0e7f3095c3
2 files changed
+22 -13

No files matched your search

+14 -7
View File
@@ -269,14 +269,11 @@ mod tests {
assert_eq!(ran.text, format!("8 {}", u16::MAX / 8)); assert_eq!(ran.text, format!("8 {}", u16::MAX / 8));
} }
#[test] /// Starts `script`, waits for it to print READY, closes it, and waits for the reap's log.
fn closing_a_session_hangs_up_its_program() { fn close_after_ready(script: &str, reaped: &str) {
crate::testlog::init(); crate::testlog::init();
let launch = Launch { let launch = Launch {
program: Some(( program: Some(("/bin/sh".into(), vec!["-c".into(), script.into()])),
"/bin/sh".into(),
vec!["-c".into(), "printf READY; exec cat".into()],
)),
cwd: std::env::temp_dir(), cwd: std::env::temp_dir(),
env: Vec::new(), env: Vec::new(),
}; };
@@ -295,7 +292,7 @@ mod tests {
}); });
let pid = pty.pid; let pid = pty.pid;
drop(pty); drop(pty);
let hung_up = format!("pty {pid}: hung up, signal: 1 (SIGHUP)"); let hung_up = format!("pty {pid}: hung up, {reaped}");
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(5);
while !crate::testlog::logged(&hung_up) { while !crate::testlog::logged(&hung_up) {
assert!(std::time::Instant::now() < deadline, "no `{hung_up}`"); assert!(std::time::Instant::now() < deadline, "no `{hung_up}`");
@@ -306,6 +303,16 @@ mod tests {
assert!(!alive, "the program was reaped"); assert!(!alive, "the program was reaped");
} }
#[test]
fn closing_a_session_hangs_up_its_program() {
close_after_ready("printf READY; exec cat", "signal: 1 (SIGHUP)");
}
#[test]
fn a_program_that_ignores_sighup_ends_when_the_pty_closes() {
close_after_ready("trap '' HUP; printf READY; exec cat", "exit status: 0");
}
#[test] #[test]
fn a_missing_folder_stops_the_start() { fn a_missing_folder_stops_the_start() {
let launch = Launch { let launch = Launch {
+8 -6
View File
@@ -3,7 +3,7 @@ use std::{
ffi::{CStr, CString}, ffi::{CStr, CString},
fs::File, fs::File,
io::{self, ErrorKind, Read}, io::{self, ErrorKind, Read},
mem::MaybeUninit, mem::{ManuallyDrop, MaybeUninit},
os::{ os::{
fd::AsRawFd, fd::AsRawFd,
unix::{ffi::OsStrExt, net::UnixStream, process::CommandExt}, unix::{ffi::OsStrExt, net::UnixStream, process::CommandExt},
@@ -35,7 +35,7 @@ const CHILD: usize = 1;
/// A program on a pseudo-terminal, started here so a failed `chdir` fails the start. /// A program on a pseudo-terminal, started here so a failed `chdir` fails the start.
pub(crate) struct Pty { pub(crate) struct Pty {
child: Child, child: Child,
file: File, file: ManuallyDrop<File>,
signals: UnixStream, signals: UnixStream,
sig: SigId, sig: SigId,
} }
@@ -119,7 +119,7 @@ pub(crate) fn spawn(
} }
Ok(Pty { Ok(Pty {
child, child,
file: File::from(master), file: ManuallyDrop::new(File::from(master)),
signals, signals,
sig, sig,
}) })
@@ -214,7 +214,7 @@ impl EventedReadWrite for Pty {
) -> io::Result<()> { ) -> io::Result<()> {
interest.key = READ_WRITE; interest.key = READ_WRITE;
unsafe { unsafe {
poll.add_with_mode(&self.file, interest, mode)?; poll.add_with_mode(&*self.file, interest, mode)?;
poll.add_with_mode(&self.signals, Event::readable(CHILD), PollMode::Level) poll.add_with_mode(&self.signals, Event::readable(CHILD), PollMode::Level)
} }
} }
@@ -226,12 +226,12 @@ impl EventedReadWrite for Pty {
mode: PollMode, mode: PollMode,
) -> io::Result<()> { ) -> io::Result<()> {
interest.key = READ_WRITE; interest.key = READ_WRITE;
poll.modify_with_mode(&self.file, interest, mode)?; poll.modify_with_mode(&*self.file, interest, mode)?;
poll.modify_with_mode(&self.signals, Event::readable(CHILD), PollMode::Level) poll.modify_with_mode(&self.signals, Event::readable(CHILD), PollMode::Level)
} }
fn deregister(&mut self, poll: &Arc<Poller>) -> io::Result<()> { fn deregister(&mut self, poll: &Arc<Poller>) -> io::Result<()> {
poll.delete(&self.file)?; poll.delete(&*self.file)?;
poll.delete(&self.signals) poll.delete(&self.signals)
} }
@@ -284,6 +284,8 @@ impl Drop for Pty {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { libc::kill(self.child.id() as libc::pid_t, libc::SIGHUP) }; unsafe { libc::kill(self.child.id() as libc::pid_t, libc::SIGHUP) };
unregister(self.sig); unregister(self.sig);
// Closed before the wait, so a program that ignores SIGHUP reads EOF.
unsafe { ManuallyDrop::drop(&mut self.file) };
match self.child.wait() { match self.child.wait() {
Ok(status) => log::info!("pty {}: hung up, {status}", self.id()), Ok(status) => log::info!("pty {}: hung up, {status}", self.id()),
Err(error) => log::error!("pty {}: cannot reap the program: {error}", self.id()), Err(error) => log::error!("pty {}: cannot reap the program: {error}", self.id()),