From 43bbd8c9961e1911ea529843c8dfeac119998c2c Mon Sep 17 00:00:00 2001 From: alanhc Date: Wed, 2 Sep 2026 03:44:40 +0800 Subject: [PATCH] Bound the pty hangup reads with an alarm The hangup checks in tests/test-pty.c read from a master whose slaves have all closed. Linux answers EIO there, but on a tree without the support nothing fails the read at all: elfuse's keepalive slave holds the host pty open, so the host has no reason to. The read parks, and a run against such a tree stalls instead of reporting. A control run took ten minutes and had to be killed. A test that guards a fix has to fail when the fix is absent. tests/test-devpts.c already takes that position for its pty round trip, and the readv case at the bottom of this file does too, with signal(SIGALRM, ...) plus alarm(10) around the blocking call and alarm(0) once it returns. The first hangup block never got the same treatment. It gets it now, once for each read. The EIO read is the wedge, and the drain before it reads the same hung-up master with no deadline of its own. Each read gets its own window, cleared as soon as the read returns, so the alarm is never armed while a verdict prints. TEST() prints its label without a newline, so a single window spanning the drain verdict could fire mid-line and blame a read that did not block. Hoisting the EIO read out of EXPECT_TRUE is what lets the alarm be cleared before its verdict, as the readv case does. The handler was silent, and its _exit(2) discarded whatever stdout still held, so a wedged run through a pipe produced no output at all -- neither the results collected so far nor a word about why it stopped. It now names the timeout, and stdout is line-buffered, so the log ends with every check that did complete followed by the reason the run went no further. The message goes to stdout rather than stderr because test-matrix.sh's run_elfuse discards stderr, and that lane is the one most likely to meet a timeout with nobody watching. Measured against a tree with proc_pty_master_hung_up stubbed to false, which is what "without the support" means here. Before: the run sat past a 60 second cap having printed nothing. After: the POLLHUP check fails, the drain passes, the EIO read trips the alarm at ten seconds, and the process exits 2 with 37 lines of results behind it. An unmodified tree still reports 70 passed, 0 failed. --- tests/test-pty.c | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/test-pty.c b/tests/test-pty.c index 5ff81422..a8173f48 100644 --- a/tests/test-pty.c +++ b/tests/test-pty.c @@ -88,11 +88,17 @@ int passes = 0, fails = 0; /* A master with no hangup support blocks these reads forever; the alarm turns - * that into a visible failure instead of a wedged run. + * that into a visible failure instead of a wedged run. The message goes to + * stdout because test-matrix.sh discards stderr. The alarm is armed only around + * a read, where stdout is at a line boundary. */ static void hup_on_alarm(int sig) { (void) sig; + static const char msg[] = + "\ntest-pty: TIMEOUT waiting on a hung-up master read\n"; + ssize_t ignored = write(STDOUT_FILENO, msg, sizeof(msg) - 1); + (void) ignored; _exit(2); } @@ -123,6 +129,9 @@ static int count_pts_entries(void) int main(void) { + /* Keep completed results when the alarm's _exit(2) fires under a pipe. */ + setvbuf(stdout, NULL, _IOLBF, 0); + printf("test-pty: PTY ioctl + /dev/pts/N path support\n"); /* Regression guard for the pty_keepalive_table BSS-zero collision: any @@ -804,20 +813,30 @@ int main(void) EXPECT_TRUE(hr > 0 && (hp.revents & POLLHUP), "no POLLHUP after the last slave closed"); + /* Both reads block forever without hangup support. */ + signal(SIGALRM, hup_on_alarm); + char hbuf[16]; - ssize_t drained = put == (ssize_t) (sizeof(bye) - 1) - ? read(hup_master, hbuf, sizeof(hbuf)) - : -1; + ssize_t drained = -1; + if (put == (ssize_t) (sizeof(bye) - 1)) { + alarm(10); + drained = read(hup_master, hbuf, sizeof(hbuf)); + alarm(0); + } TEST("queued output survives the hangup"); EXPECT_TRUE(drained == (ssize_t) (sizeof(bye) - 1) && memcmp(hbuf, bye, sizeof(bye) - 1) == 0, "pending slave output was lost"); errno = 0; + alarm(10); + ssize_t hret = read(hup_master, hbuf, sizeof(hbuf)); + int herr = errno; + alarm(0); + TEST("read reports EIO once drained"); - EXPECT_TRUE( - read(hup_master, hbuf, sizeof(hbuf)) < 0 && errno == EIO, - "read did not report the hangup as EIO"); + EXPECT_TRUE(hret < 0 && herr == EIO, + "read did not report the hangup as EIO"); } close(hup_master); }