Skip to content

Worker threads: module-mode scripts (#410), ctor-failure Errors (#464), event-driven bridge (#465)#710

Merged
nickna merged 1 commit into
mainfrom
wrk/friendly-hermann-fad5ce
Jun 16, 2026
Merged

Worker threads: module-mode scripts (#410), ctor-failure Errors (#464), event-driven bridge (#465)#710
nickna merged 1 commit into
mainfrom
wrk/friendly-hermann-fad5ce

Conversation

@nickna

@nickna nickna commented Jun 16, 2026

Copy link
Copy Markdown
Owner

Implements three worker_threads issues. All changes are runtime-only (interpreter + IL emitter); the type checker is untouched.

#410 — worker scripts support import

A worker script that uses import/export now runs through the same module pipeline the parent uses (ModuleResolverCheckModulesInterpretModules) instead of the bare single-file path, which rejected every import at type-check (Import statements require module mode) — and silently swallowed the failure via the worker's error event.

  • SharpTSWorker.RunWorkerScript detects module mode (UsesModuleSyntax: import/export/triple-slash-path/CJS — mirrors Program.RunFile) and routes to a module or single-file path accordingly.
  • import { workerData, parentPort, threadId, isMainThread } from "worker_threads" resolves to the running worker's live values via a new internal Interpreter.WorkerThreadsContext, injected into the worker_threads module exports in ExecuteModule. The bound parentPort is the same instance as the bare global, so a listener attached through the import receives the messages the worker's message loop delivers.
  • Relative-module imports inside workers work too (full pipeline, not a worker_threads special-case). Script-mode workers keep the existing single-file path unchanged.

#464 — Worker construction failures surface as Error, not a string

A native built-in constructor that fails with a plain host exception (e.g. new Worker(...) failing the workerData structured-clone) now reaches a guest try/catch as a real Error (e.message, e.stack, e instanceof Error), matching compiled mode.

  • Wrapped at the EvaluateNew native-callable dispatch (sync + async) via IsNativeConstructorFailure, which excludes ThrowException (guest throws routed back out) and SharpTSException (interpreter-internal errors deliberately kept as message strings).
  • A central TranslateException change would be unsafe: ThrowException.FromResult turns string guest-throws into a plain System.Exception, making them type-indistinguishable from native errors (an early attempt there regressed 23 generator/async tests).

#465CompiledMessagePortBridge follow-ups

  • (1) Event-driven receive. The emitted $MessagePort gains an _onEnqueue Action that a parent post invokes (volatile read, paired with a MemoryBarrier on the bridge's write) to wake the worker loop via the thread-safe EnqueueCallback, replacing the 10ms poll timer — an idle bridged port no longer wakes the worker loop 100×/second. The keep-alive Ref still holds the loop open while the port is open.
  • (2) receiveMessageOnPort on a transferred compiled port via CompiledMessagePortBridge.ReceiveMessageSync; both receiveMessageOnPort builtins now accept the bridge.
  • Item 3 (worker→worker port transfer) is architectural and remains documented in Follow-ups for compiled→worker MessagePort transfer (CompiledMessagePortBridge) #465.

Testing

  • New regression tests in WorkerThreadsTests.cs for all three issues (both execution modes).
  • Full SharpTS.Tests: 12,700 passed / 0 failed.
  • Test262 conformance-neutral in both modes: the interpreted (18 reg / 186 pass) and compiled (40 reg / 65 pass) baseline diffs are identical with and without these changes (verified by stashing and re-running on clean main), so the committed baselines are left untouched.
  • The emitted-IL change adds only a BCL Action field, so compiled output stays standalone (no SharpTS.dll reference).

Notes for reviewer

Closes #410
Closes #464

…, event-driven bridge (#465)

#410 — worker scripts support `import`:
Run a worker script that uses import/export through the full module pipeline
(ModuleResolver → CheckModules → InterpretModules) instead of the bare
single-file path that rejected every import at type-check ("Import statements
require module mode"). `import { workerData, parentPort, threadId, isMainThread }
from "worker_threads"` now resolves to the running worker's live values via
Interpreter.WorkerThreadsContext (injected into the worker_threads module
exports in ExecuteModule). The bound parentPort is the same instance as the
bare global, so a listener attached via the import receives delivered messages.
Relative-module imports in workers work too. Script-mode workers keep the
single-file path unchanged.

#464 — Worker construction failures surface as Error, not a string:
A native built-in constructor that fails with a plain host exception (e.g.
`new Worker(...)` failing the workerData structured-clone) now surfaces to a
guest try/catch as a real Error (e.message, e.stack, e instanceof Error),
matching compiled mode. Wrapped at the EvaluateNew native-callable dispatch
(sync + async) via IsNativeConstructorFailure, which excludes ThrowException
(guest throws routed back out) and SharpTSException (interpreter-internal
errors kept as message strings per the ThrowException.FromResult contract).
A central TranslateException change is unsafe: FromResult deliberately turns
string guest-throws into plain System.Exception, making them
type-indistinguishable from native errors.

#465 — CompiledMessagePortBridge follow-ups:
(1) Receive path is now event-driven. The emitted $MessagePort gets an
_onEnqueue Action that a parent post invokes (volatile read, paired with a
MemoryBarrier on the bridge's write) to wake the worker loop via the
thread-safe EnqueueCallback, replacing the 10ms poll timer — an idle bridged
port no longer wakes the worker loop 100x/second. The keep-alive Ref still
holds the loop open while the port is open.
(2) receiveMessageOnPort(port) now works on a transferred compiled port
(CompiledMessagePortBridge.ReceiveMessageSync); both receiveMessageOnPort
builtins accept the bridge.
Item 3 (worker→worker port transfer) remains documented in #465.

Tests: new regression tests in WorkerThreadsTests.cs (both modes) for all
three. Full SharpTS.Tests green (12700/0). Test262 conformance-neutral:
interpreted and compiled baseline diffs are identical with and without these
changes (pre-existing baseline staleness on main is left untouched). Filed
#700 (compiled WrapException yields a plain {message,name} object, not a real
Error) and #701 (compiled property access on undefined does not throw) for
compiled-mode error-shape gaps found en route.
@nickna nickna merged commit d906faa into main Jun 16, 2026
3 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

1 participant