diff --git a/nebula_terminal/src/event_loop.rs b/nebula_terminal/src/event_loop.rs index 30e0397..207ff4f 100644 --- a/nebula_terminal/src/event_loop.rs +++ b/nebula_terminal/src/event_loop.rs @@ -480,6 +480,16 @@ where self.event_proxy.send_event(Event::Wakeup); } + // Boot profiling: a readable wake that carried no bytes is the + // signature of a lost or spurious wakeup; report only the first few. + if processed == 0 { + use std::sync::atomic::{AtomicUsize, Ordering}; + static EMPTY_READS: AtomicUsize = AtomicUsize::new(0); + if EMPTY_READS.fetch_add(1, Ordering::Relaxed) < 8 { + crate::pty_trace("pty_read: readable wake with no bytes"); + } + } + Ok(processed) } diff --git a/nebula_terminal/src/tty/windows/blocking.rs b/nebula_terminal/src/tty/windows/blocking.rs index 644b926..9f46cec 100644 --- a/nebula_terminal/src/tty/windows/blocking.rs +++ b/nebula_terminal/src/tty/windows/blocking.rs @@ -70,6 +70,7 @@ impl UnblockedReader { Poll::Ready(Ok(0)) => { // Either the pipe is closed or the reader is at its EOF. // In any case, we are done. + crate::pty_trace("conout reader thread: EOF"); return; }, @@ -84,10 +85,14 @@ impl UnblockedReader { }, // Windows reports normal anonymous-pipe EOF this way. - Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::BrokenPipe => return, + Poll::Ready(Err(e)) if e.kind() == io::ErrorKind::BrokenPipe => { + crate::pty_trace("conout reader thread: broken pipe"); + return; + }, Poll::Ready(Err(e)) => { log::error!("error writing to pipe: {}", e); + crate::pty_trace("conout reader thread: read error"); return; }, diff --git a/scripts/conformance/conpty_smoke.py b/scripts/conformance/conpty_smoke.py index 94e679f..7a26cf3 100644 --- a/scripts/conformance/conpty_smoke.py +++ b/scripts/conformance/conpty_smoke.py @@ -225,7 +225,8 @@ def run_case( result["exit"] = code.value # The in-box host flushes trailing output while ClosePseudoConsole drains # the pipe; the reader thread keeps consuming during that call. - kernel.CloseHandle(conout_write) + if conout_write is not None: + kernel.CloseHandle(conout_write) close(hpc) thread.join(2.0) output = b"".join(chunks) @@ -238,7 +239,8 @@ def run_case( kernel.CloseHandle(process.hProcess) kernel.DeleteProcThreadAttributeList(attributes) for handle in (conin_write, conin_read, conout_read): - kernel.CloseHandle(handle) + if handle is not None: + kernel.CloseHandle(handle) return result @@ -294,7 +296,12 @@ def main() -> int: b"133;A", ), } - variants: list[tuple[str, ...]] = [(), ("resize",), ("probe",), ("resize", "probe", "probe")] + variants: list[tuple[str, ...]] = [ + (), + ("resize", "probe", "probe"), + ("early_close",), + ("early_close", "resize", "probe", "probe"), + ] for name, (command, marker) in interactive.items(): for steps in variants: case = run_case(