Skip to content

feat(worker_threads): worker.stdin Writable (parent→worker process.stdin bridge) (#1076)#1251

Merged
nickna merged 1 commit into
mainfrom
worktree-issue-1076
Jul 5, 2026
Merged

feat(worker_threads): worker.stdin Writable (parent→worker process.stdin bridge) (#1076)#1251
nickna merged 1 commit into
mainfrom
worktree-issue-1076

Conversation

@nickna

@nickna nickna commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Closes #1076. Follow-up to #1003, closing the last stdio gap of epic #996.

What

With stdin: true, worker.stdin is now a Writable on the parent whose writes are bridged into the worker's process.stdin Readable — the parent→worker mirror of #1003's worker→parent stdout/stderr. worker.stdin.write(chunk) surfaces as 'data' inside the worker; worker.stdin.end() surfaces as 'end'.

The wrinkle (and a latent bug fixed)

The worker's process.stdin resolved to the process-global SharpTSStdin.Instance, which spawns a background thread reading the real Console.In — so a worker would consume the host terminal.

Fix: a [ThreadStatic] WorkerThreads.WorkerStdin (mirroring the existing ClusterContext pattern), installed on the worker's dedicated thread in RunWorkerScript and consulted by ProcessBuiltIns.GetOwnMember("stdin"). It is always isolated to a per-worker SharpTSReadable (even without stdin: true) so a worker never touches the terminal, and is only fed from the parent when stdin: true is passed.

Bridge

  • Parent-side worker.stdin is a SharpTSWritable created only on stdin: true (else GetMember returns null → reads as undefined, consistent with worker.stdout/stderr).
  • Its write/final callbacks (mirroring the child_process stdin bridge) enqueue chunks / a null-EOF sentinel onto a dedicated BlockingCollection.
  • The existing WorkerMessageHandler poll timer drains it each tick via new PumpStdinPushFromHost into the worker's process.stdin (null ⇒ 'end'). No new cross-thread marshaling primitive; same lifecycle/guards as the message queue.

A dedicated queue (not the shared ClonedMessage record) is used because stdin and postMessage are separate channels in Node.

Scope note

A worker started without stdin: true gets an isolated stdin that stays open (never emits 'end') rather than ending immediately as Node does — strictly better than the prior behavior and not worth the edge-case complexity here.

Tests (dual-mode)

  • worker.stdin.write(...) → readable in the worker via process.stdin 'data'; end()'end'.
  • Multiple writes preserve order (accumulate + flush on 'end').
  • Un-piped worker.stdin is absent (falsy).

Verification

  • Full xUnit: 15421 / 0 (incl. 123 WorkerThreadsTests)
  • Test262 (interp + compiled): 22 / 0 / 4 skip (baseline)
  • TypeScript conformance: 31 / 0
  • Build clean (pre-existing warnings only)

…din bridge) (#1076)

Follow-up to #1003, closing the last stdio gap of epic #996. With `stdin: true`,
`worker.stdin` is now a Writable on the parent whose writes are bridged into the
worker's `process.stdin` Readable (the reverse of #1003's worker→parent stdout).

The worker's `process.stdin` previously resolved to the process-global
`SharpTSStdin.Instance`, which reads the real `Console.In` — so a worker would
consume the host terminal. Fix: a `[ThreadStatic] WorkerThreads.WorkerStdin`
(mirroring `ClusterContext`) installed on the worker thread, consulted by
`ProcessBuiltIns.GetOwnMember`. It is always isolated (even without `stdin: true`)
so a worker never touches the terminal, and only fed when the parent opts in.

The bridge reuses the existing `WorkerMessageHandler` poll timer (new `PumpStdin`)
draining a dedicated queue — no new cross-thread marshaling primitive. The parent
`_stdin` Writable's write/final callbacks mirror the child_process stdin bridge.

Dual-mode tests: write→'data' + end→'end' roundtrip, multi-write ordering, and
un-piped stdin is absent.
@nickna nickna merged commit fb1baa6 into main Jul 5, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

worker_threads: worker.stdin Writable (parent→worker process.stdin bridge)

1 participant