You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(event): reset the process-global reader across replaced terminals #80
Issue #77 makes EOF/HUP terminal input a stable error in one Unix event source. Crossterm's process-global INTERNAL_EVENT_READER, however, owns that source for the process lifetime. After an embedded/public tut::run returns from terminal EOF, replacing fd0 with a new tty and calling tut::run again still reaches the old source's terminal_closed latch.
Blindly dropping or rebuilding the source is unsafe when event-stream is enabled: EventStream caches the old source's poll waker, so a worker blocked on a new generation may no longer be woken on Drop.
The CLI normally exits after #77 and is not affected. This issue tracks the reusable-library/process-global lifecycle separately rather than expanding the bounded EOF fix.
Required design
Give the global reader an explicit source generation and a stable terminal identity.
Preserve a stable EOF for the failed generation while the underlying tty identity is unchanged.
When a later session presents a different valid tty, initialize exactly one new source generation before reading it.
Make EventStream obtain/wake the active generation; no cached stale waker may strand a worker.
Deliver source errors deterministically—do not lose an EOF between the worker and executor, duplicate it into an unrelated generation, or spin while retrying.
Design audit after 0.0.4 confirms that this must be a generation-aware lifecycle change, not source = UnixInternalEventSource::new() after EOF.
Additional constraints discovered while tracing both Unix backends:
A generation needs an owned tty descriptor and a stable identity derived from the opened object (at minimum device/inode/rdev as available), rather than repeatedly interpreting mutable fd 0.
The use-dev-tty backend currently relies on process-lifetime registration of its SIGWINCH pipe. Rebuilding sources without an RAII registration/unregistration owner would leak registrations and descriptors across generations.
EventStream cannot cache one source waker for its whole lifetime. The reader should expose an atomic active-generation snapshot, and worker results/errors must carry that generation so EOF cannot be lost, duplicated into a replacement terminal, or delivered to the wrong executor turn.
Stream drop/shutdown must re-read and wake the current generation, with a second generation check around worker publication; waking only the constructor-time source can strand a worker after replacement.
An unchanged closed identity must retain the existing stable EOF without repeated construction. A different valid identity should be built completely—including wake and signal resources—before one transactional publish.
The safest implementation order is therefore: (1) source-owned tty identity and registration RAII, (2) generation-tagged global reader state and transactional replacement, (3) generation-aware EventStream worker/result/waker handoff, then (4) the public tty-A/EOF/tty-B end-to-end test. A narrower singleton reset would satisfy the synchronous happy path while leaving stream hangs and use-dev-tty resource leaks, so it should not be merged independently.
Completed by signed crossterm fork commit e3888223b5064543484cc08c3966cc05d711003a and TUT merge fe91fc4e3b1f0dbe6d13e566d187a3eab285fd89 (PR #106).
Final evidence:
public same-process real-PTY test observes terminal A EOF, rebinds fd 0 to terminal B, and completes a second tut::run after reading B input;
unchanged closed identities retain stable typed EOF; candidate initialization failure preserves the original error and old generation;
sync reader serialization, generation-atomic replacement, EventStream EOF handoff/current-waker Drop, and SIGWINCH registration RAII are independently tested across both Unix backends;
full SHA pin is present in config, lockfile, and packaged source;
current, Rust 1.88, macOS, Linux cross-check, overflow, bounded-results, package/distcheck, PR CI, and post-merge main CI all passed (run 31474755219).
Activity
m1ngsama commentedon Aug 10, 2026
Design audit after 0.0.4 confirms that this must be a generation-aware lifecycle change, not
source = UnixInternalEventSource::new()after EOF.Additional constraints discovered while tracing both Unix backends:
use-dev-ttybackend currently relies on process-lifetime registration of its SIGWINCH pipe. Rebuilding sources without an RAII registration/unregistration owner would leak registrations and descriptors across generations.EventStreamcannot cache one source waker for its whole lifetime. The reader should expose an atomic active-generation snapshot, and worker results/errors must carry that generation so EOF cannot be lost, duplicated into a replacement terminal, or delivered to the wrong executor turn.The safest implementation order is therefore: (1) source-owned tty identity and registration RAII, (2) generation-tagged global reader state and transactional replacement, (3) generation-aware EventStream worker/result/waker handoff, then (4) the public tty-A/EOF/tty-B end-to-end test. A narrower singleton reset would satisfy the synchronous happy path while leaving stream hangs and
use-dev-ttyresource leaks, so it should not be merged independently.m1ngsama commentedon Aug 11, 2026
Completed by signed crossterm fork commit
e3888223b5064543484cc08c3966cc05d711003aand TUT mergefe91fc4e3b1f0dbe6d13e566d187a3eab285fd89(PR #106).Final evidence:
tut::runafter reading B input;