Skip to content

feat(worker_threads): compiled main-thread receiveMessageOnPort drain + MessageChannel messageerror (#1077)#1253

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

feat(worker_threads): compiled main-thread receiveMessageOnPort drain + MessageChannel messageerror (#1077)#1253
nickna merged 1 commit into
mainfrom
worktree-issue-1077

Conversation

@nickna

@nickna nickna commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Closes #1077.

Fixes the two compiled-mode gaps documented during epic #996 for the main-thread MessageChannel/$MessagePort path. The worker-driven paths were already dual-mode; this brings the interpreter-only MessageChannel cases to parity.

1. Synchronous receiveMessageOnPort (from #1000)

$Runtime.WorkerThreadsReceiveMessageOnPort was an unconditional undefined stub: $MessagePort is emitted by EmitMessageChannelTypes after EmitRuntimeClass, so the helper couldn't reference the port's _pending queue at emit time.

Fix: the method is still defined during EmitWorkerThreadsModuleHelpers (so call sites bind the token), but its body is now emitted afterward by a new EmitWorkerThreadsReceiveMessageOnPortBody, wired in right after EmitMessageChannelTypes and before EmitRuntimeClassFinalize (which closes $Runtime). The body synchronously dequeues one message from the port's own _pending queue and returns { message } (a Dictionary<string, object?>), else undefined — matching SharpTSMessagePort.ReceiveMessageSync.

2. messageerror on postMessage of an uncloneable value (from #1001)

The emitted structured clone (cloneCore) returns uncloneable values by reference and never throws, so a compiled $MessagePort had no clone-failure point and always delivered message.

Fix (confined to $MessagePort, per the issue's second option): PostMessage detects the uncloneable categories up front (typeof "function" — covers functions/classes/bound-callable wrappers/delegates — and "symbol") and enqueues a shared static _cloneError sentinel instead of a clone. Drain compares by reference and emits 'messageerror' (no args) instead of 'message'. This mirrors the interpreter's receiver-side model (ClonedMessage(IsError: true)) without touching the shared cloneCore, so compiled structuredClone / $BroadcastChannel are unaffected.

Notes

  • Both paths stay fully standalone — pure emitted IL, no SharpTS.dll dependency; verified with --compile --verify and --compile --standalone --verify (both clean, no SharpTS.dll co-located).
  • Deeply nested uncloneables (e.g. { fn: () => {} }) remain aliased, consistent with the emitted clone's existing shallow fallback — documented inline.
  • Observed (pre-existing, out of scope): a compiled $MessagePort Refs the event loop on Start() unconditionally, unlike the interpreter which only Refs cross-thread ports. The existing compiled MessageChannel tests already work around this by closing ports; the new messageerror test follows that convention.

Tests

  • ReceiveMessageOnPort_QueuedMessage_ReturnsMessageThenUndefined — new, dual-mode ({ message } then undefined).
  • MessageChannelPort_PostUncloneable_FiresMessageError — was an interpreter-only [Fact], now a dual-mode [Theory].

Green: xUnit 15428/0 · Test262 22/0/4skip (interp + compiled) · TSConf 31/0.

… + MessageChannel messageerror (#1077)

Closes the two compiled-mode gaps documented during epic #996 for the
main-thread MessageChannel/$MessagePort path (the worker-driven paths were
already dual-mode).

1. Synchronous receiveMessageOnPort (#1000 follow-up)
   $Runtime.WorkerThreadsReceiveMessageOnPort was an unconditional undefined
   stub because $MessagePort is emitted (EmitMessageChannelTypes) AFTER
   EmitRuntimeClass, so the helper could not reference the port queue at emit
   time. Split it: define the method during EmitWorkerThreadsModuleHelpers (so
   callers bind the token) and emit its BODY afterward via a new
   EmitWorkerThreadsReceiveMessageOnPortBody, wired right after
   EmitMessageChannelTypes and before EmitRuntimeClassFinalize. The body drains
   one message from the port's own _pending queue and returns { message }, else
   undefined - matching SharpTSMessagePort.ReceiveMessageSync.

2. messageerror on postMessage of an uncloneable value (#1001 follow-up)
   The emitted structured clone (cloneCore) returns uncloneable values by
   reference and never throws, so a compiled $MessagePort had no clone-failure
   point. $MessagePort.PostMessage now detects the uncloneable categories up
   front (typeof "function"/"symbol") and enqueues a shared static _cloneError
   sentinel; Drain compares by reference and emits 'messageerror' (no args)
   instead of 'message'. This mirrors the interpreter's receiver-side model
   (ClonedMessage IsError: true) without touching the shared cloneCore, so
   structuredClone/$BroadcastChannel are unaffected.

Both paths stay fully standalone (pure emitted IL, no SharpTS.dll dependency;
--standalone --verify clean). Deeply nested uncloneables remain aliased,
consistent with the emitted clone's existing shallow fallback.

Tests: ReceiveMessageOnPort_QueuedMessage_ReturnsMessageThenUndefined (new,
dual-mode) and MessageChannelPort_PostUncloneable_FiresMessageError (was
interpreter-only Fact -> dual-mode Theory).

xUnit 15428/0, Test262 22/0/4skip, TSConf 31/0.
@nickna

nickna commented Jul 5, 2026

Copy link
Copy Markdown
Owner Author

Filed the two pre-existing divergences surfaced during this work as follow-ups:

Both are out of scope for #1077 and left as-is here.

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: compiled main-thread MessageChannel messageerror + synchronous receiveMessageOnPort

1 participant