Summary
At rio revision 7ae087500bcde5c0c9f09cb9c50382e9220b3360, the Unix PTY path has two independently actionable lifecycle defects:
- a reaped child can later be signalled through its saved numeric PID;
- final PTY output can be lost during exit under terminal-state lock contention.
Final-Output Failure
A real PTY child can write a final marker, exit 0, emit child-exit/close, and leave the marker absent from the final frame. This is reproduced with coordinated snapshot-lock contention (timing-dependent).
Machine::pty_read buffers bytes locally. After failed lock acquisition it may read again; Linux PTY hangup may then yield EIO. The error path returns before the already-read bytes reach the parser. This mechanism is source-backed and consistent with the failure; the regression does not prove the exact syscall sequence.
Child Lifecycle Risk
Child::waitpid(&self) reaps without recording completion; Child::drop unconditionally sends SIGHUP to the saved PID. PID reuse can therefore signal an unrelated process. This is a source-level risk, not an observed incident. PID reuse does not cause the output-loss failure.
API Boundary
Pty.child and Child.pid are public, but Child.process, Machine.pty, and State.parser are private. There is no small ownership-safe repair while retaining Machine<Pty, Listener>. A custom EIO-to-EOF reader only mitigates one path.
Expected Repair
- Make termination/reap explicit, idempotent, status-preserving, and incapable of signalling after reap.
- Parse/preserve all buffered bytes before EOF/HUP/error; publish exit only after final drain and pending synchronized updates.
- Define bounded handling for descendants holding the slave open.
Regression Coverage
Add a synthetic EventedPty bytes → EIO test under lock contention; EOF/HUP residual output; parsing-budget and pending-sync cases; plus real-child natural exit, repeated close, ignored SIGHUP, reap completion, setup/reader failures, and no post-reap signal.
Happy to provide the embedding-side regression harness.
Summary
At rio revision
7ae087500bcde5c0c9f09cb9c50382e9220b3360, the Unix PTY path has two independently actionable lifecycle defects:Final-Output Failure
A real PTY child can write a final marker, exit 0, emit child-exit/close, and leave the marker absent from the final frame. This is reproduced with coordinated snapshot-lock contention (timing-dependent).
Machine::pty_readbuffers bytes locally. After failed lock acquisition it may read again; Linux PTY hangup may then yieldEIO. The error path returns before the already-read bytes reach the parser. This mechanism is source-backed and consistent with the failure; the regression does not prove the exact syscall sequence.Child Lifecycle Risk
Child::waitpid(&self)reaps without recording completion;Child::dropunconditionally sendsSIGHUPto the saved PID. PID reuse can therefore signal an unrelated process. This is a source-level risk, not an observed incident. PID reuse does not cause the output-loss failure.API Boundary
Pty.childandChild.pidare public, butChild.process,Machine.pty, andState.parserare private. There is no small ownership-safe repair while retainingMachine<Pty, Listener>. A custom EIO-to-EOF reader only mitigates one path.Expected Repair
Regression Coverage
Add a synthetic
EventedPtybytes → EIO test under lock contention; EOF/HUP residual output; parsing-budget and pending-sync cases; plus real-child natural exit, repeated close, ignored SIGHUP, reap completion, setup/reader failures, and no post-reap signal.Happy to provide the embedding-side regression harness.