diff options
| -rw-r--r-- | src/tty.zig | 39 | ||||
| -rw-r--r-- | test/snapshots/winch.golden | 66 | ||||
| -rw-r--r-- | test/snapshots/winch.snap | 19 |
3 files changed, 123 insertions, 1 deletions
diff --git a/src/tty.zig b/src/tty.zig index 7de76f4d..a2ea6462 100644 --- a/src/tty.zig +++ b/src/tty.zig @@ -47,6 +47,20 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { const io = init.io; const gpa = init.gpa; + // SIGWINCH must never run vaxis's signal handler: it posts the winsize + // event through std.Io.Mutex/Condition, and when the signal lands on a + // thread blocked inside an Io.Threaded syscall region (pty readers in + // read(2), the main thread parked in queue.pop) a contended lock re-enters + // the Io machinery and Syscall.start hits `unreachable` — panic, then the + // panic-time terminal restore used to write through the same Io and + // recurse until stack overflow. Reproduced by resizing the outer terminal + // (e.g. a font-size change) while shells run. Block it here, before any + // thread exists (threads inherit the mask, so vaxis's handler never + // fires), and take it synchronously on the sigwait thread below instead. + var winch_set = posix.sigemptyset(); + posix.sigaddset(&winch_set, posix.SIG.WINCH); + posix.sigprocmask(posix.SIG.BLOCK, &winch_set, null); + var tty_buf: [0x10000]u8 = undefined; var tty = try vaxis.Tty.init(io, &tty_buf); defer tty.deinit(); // restore cooked termios LAST, after vx flushed its resets @@ -107,7 +121,9 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void { try loop.start(); defer loop.stop(); - try loop.installResizeHandler(); + // resize watcher: plain detached thread (not io.concurrent — teardown + // joins those, and sigwait never returns); dies with the process + (try std.Thread.spawn(.{}, winchWatch, .{ &loop, &vx, &tty })).detach(); vx.queryTerminal(tty.writer(), std.Io.Duration.fromMilliseconds(2)) catch {}; // now threads are fine: start a reader task per pty @@ -354,11 +370,32 @@ fn drainEffects( }; } +/// Consume SIGWINCH synchronously (it is blocked in every thread) and post +/// the new size as a winsize event from normal thread context — the one place +/// vaxis's Io-backed queue is safe to touch on a resize. +fn winchWatch(loop: *Loop, vx: *vaxis.Vaxis, tty: *vaxis.Tty) void { + var set = posix.sigemptyset(); + posix.sigaddset(&set, posix.SIG.WINCH); + while (true) { + var sig: c_int = 0; + if (libc.sigwait(&set, &sig) != 0) continue; + if (vx.state.in_band_resize) continue; // terminal reports via CSI 48 + const ws = tty.getWinsize() catch continue; + loop.postEvent(.{ .winsize = ws }) catch {}; + } +} + fn forkShell(cwd: ?[*:0]const u8, rows: u16, cols: u16) struct { file: std.Io.File, pid: posix.pid_t } { var master: c_int = undefined; const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 }; const pid = forkpty(&master, null, null, &ws); if (pid == 0) { + // the blocked-SIGWINCH mask survives fork AND exec — unblock it or + // bash/vim in the pane would never see resizes (sigprocmask is + // async-signal-safe) + var set = posix.sigemptyset(); + posix.sigaddset(&set, posix.SIG.WINCH); + posix.sigprocmask(posix.SIG.UNBLOCK, &set, null); if (cwd) |c| _ = chdir(c); const argv: [4:null]?[*:0]const u8 = .{ bash_path, "--rcfile", "/tmp/pardes-osc133.bash", null }; _ = execv(bash_path, &argv); diff --git a/test/snapshots/winch.golden b/test/snapshots/winch.golden new file mode 100644 index 00000000..f0b1277b --- /dev/null +++ b/test/snapshots/winch.golden @@ -0,0 +1,66 @@ +== snap storm grid=100x30 cursor=4,6 +|Kill Newcol Tutor Debug Colors NextColor Dump +| TTY /tmp/pardes-snap/winch/cwd Del NOR /tmp/pardes-snap/winch/cwd Del +| $ ls +| $ for i in $(seq 600); do kill -WINCH $PPID; don +| e; echo SUR''VIVED +| SURVIVED +| $ +| +| +| +| +| +| +| +| +| +| NOR /tmp/pardes-snap/winch/cwd Del +| +| +| +| +| +| +| +| +| +| +| +| +| +== snap resized grid=106x34 cursor=4,6 +|Kill Newcol Tutor Debug Colors NextColor Dump +| TTY /tmp/pardes-snap/winch/cwd Del NOR /tmp/pardes-snap/winch/cwd Del +| $ ls +| $ for i in $(seq 600); do kill -WINCH $PPID; done; +| echo SUR''VIVED +| SURVIVED +| $ +| +| +| +| +| +| +| +| +| +| +| +| NOR /tmp/pardes-snap/winch/cwd Del +| +| +| +| +| +| +| +| +| +| +| +| +| +| +| diff --git a/test/snapshots/winch.snap b/test/snapshots/winch.snap new file mode 100644 index 00000000..b1376ab3 --- /dev/null +++ b/test/snapshots/winch.snap @@ -0,0 +1,19 @@ +# SIGWINCH crash (reported as "crash on font-size change"): resizing the outer +# terminal delivers SIGWINCH, and vaxis's default handler locks std.Io mutexes +# from signal context — landing on a thread inside an Io.Threaded syscall +# region (pty readers in read(2), main parked in queue.pop) panicked the Io +# machinery and killed pardes. Now every thread blocks SIGWINCH and a sigwait +# watcher consumes it. The in-pane kill storm below crashed pardes before the +# fix; the resize after proves the watcher still reflows the grid. +start 30 100 -n 3 +stable 700 20000 +key c-b +stable 600 8000 +text for i in $(seq 600); do kill -WINCH $PPID; done; echo SUR''VIVED +key enter +wait 15000 SURVIVED +stable 700 15000 +snap storm +resize 34 106 +stable 700 15000 +snap resized |
