Skip to content

fix(event): reset the process-global reader across replaced terminals #80

Closed

Description

@m1ngsama

Problem

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.
  • Preserve the Bound Unix terminal input memory and sequence lifetime #74 byte/time/event bounds and fix(input): terminate cleanly when the controlling terminal closes #77 HUP ordering.

Acceptance

  • Public same-process test: run against tty A, close A, observe the exact EOF error, bind fd0 to tty B, and successfully run/read from B.
  • An unchanged closed tty continues to return the same EOF without source recreation or busy looping.
  • Concurrent poll/read remains serialized by the existing reader lock; generation changes are atomic.
  • EventStream receives the EOF once, then can use a later generation; dropping it always wakes the currently blocked worker.
  • Source initialization failure is typed and does not publish a half-initialized generation.
  • Current/MSRV strict Clippy/tests, Linux/macOS PTY tests, and event-stream configurations pass.

This is a child of #24 and a follow-up to #77.

Activity

  1. m1ngsama commented on Aug 10, 2026

    @m1ngsama
    OwnerAuthor

    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.

  2. m1ngsama commented on Aug 11, 2026

    @m1ngsama
    OwnerAuthor

    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).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions