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
Current MessagePort lifetime checkpoint — 21 September 2026
Exact merged heads are WebScene d3c030fa, AppScene 080ee9c, and unchanged Code OSS 645f29cc. Focused child #825 and PR #856 are closed/merged at 5db88657 (20 September); the prior local-only/blocked compiler-stack status is superseded.
The merged change forwards low-memory notifications through the serialized ServiceWorker runtime and retains active iframe ports across V8 contexts. Its focused iframe forced-GC gate and 100-cycle ServiceWorker gate passed, including queue, binding, V8 heap, and RSS bounds. Earlier same-realm 1,000-round-trip direct-Promise-resolver/addEventListener forced-GC gate passed with p95 0.823 ms, zero settled bindings/messages/bytes, and unchanged bounded heap/RSS; this evidence is recorded in docs/validation/messageport-active-gc-20260917.md.
#288 stays open for a cumulative 1,000-round-trip same-realm/dedicated-worker/ServiceWorker/iframe cross-platform installed-package matrix and the final inactive/listener-removal/close/transfer/navigation/termination/engine-teardown queue, binding, heap, and RSS proof. No new runtime fix is attributed without a reduced current-head failure.
Parent worker/MessagePort issue: #81.
Related integration: #265 and PR #281.
Overlapping owner: PR #245 (webscene_v8_runtime_clone.inc).
Proven gap
A started MessagePort with an installed onmessage handler can be reclaimed while JavaScript is awaiting the next message. This drops the endpoint and leaves the pending Promise unresolved. The failure becomes deterministic when the current merged CSS/runtime head is combined with the #281 Service Worker Clients tranche.
The unchanged focused loop creates a channel, assigns the Promise resolver directly, starts port1, transfers port2 into the Service Worker, and awaits the Service Worker message plus port reply. The old #281 build completes 100/100 cycles. Against current main plus #281, it repeatedly stalls near cycle 69–70 with complete:false, error:null, and no queue-capacity phase reached.
Applying PR #245 commit 94171a32 locally preserves local entangled peers but does not fix this case. Replacing channel.port1.onmessage = resolve with a closure that records state and calls resolve(event) makes five consecutive 100-cycle runs pass (p95 0.065–0.147 ms). That timing-sensitive difference indicates that the weak native wrapper is not retained by the started-port/listener reachability contract; the closure happens to keep additional JavaScript state alive.
This is a generic MessagePort lifetime defect. Do not special-case Service Workers or Promise resolver functions. The implementation path overlaps #245 and must be coordinated there instead of duplicated.
Acceptance
A minimal forced-GC native regression fails before the fix and covers both port.onmessage = promiseResolve and addEventListener('message', listener) after start().
Started ports with a message listener remain active while entangled and can receive queued/future messages even when no unrelated closure retains the wrapper.
Transfer between Window, dedicated Worker, Service Worker, iframe, and same-realm MessageChannel preserves endpoint identity, listener delivery, ordering, and exactly-once ownership.
close(), transfer, listener removal plus inactive state, navigation, worker termination, and engine teardown release native endpoints, weak handles, listeners, queue bytes, and wake registrations.
Chromium/WPT-derived tests cover active-port garbage-collection semantics and queued-message delivery.
1,000 forced-GC round trips complete without a lost wake or message; queue high-water, V8 heap, RSS, and retained binding counts stay bounded after warm-up.
Directly related macOS/Linux/Windows native and portable V8 runners pass.
Proposed fix
Extend the MessagePort reachability model in the existing #245-owned clone/runtime path: retain active ports through the browser-spec active-port condition while they are started and have message listeners, and release that retention at the exact close/transfer/listener/lifecycle boundaries. Preserve weak collection for inactive unreachable pairs.
Current MessagePort lifetime checkpoint — 21 September 2026
Exact merged heads are WebScene
d3c030fa, AppScene080ee9c, and unchanged Code OSS645f29cc. Focused child #825 and PR #856 are closed/merged at5db88657(20 September); the prior local-only/blocked compiler-stack status is superseded.The merged change forwards low-memory notifications through the serialized ServiceWorker runtime and retains active iframe ports across V8 contexts. Its focused iframe forced-GC gate and 100-cycle ServiceWorker gate passed, including queue, binding, V8 heap, and RSS bounds. Earlier same-realm 1,000-round-trip direct-Promise-resolver/addEventListener forced-GC gate passed with p95 0.823 ms, zero settled bindings/messages/bytes, and unchanged bounded heap/RSS; this evidence is recorded in
docs/validation/messageport-active-gc-20260917.md.#288 stays open for a cumulative 1,000-round-trip same-realm/dedicated-worker/ServiceWorker/iframe cross-platform installed-package matrix and the final inactive/listener-removal/close/transfer/navigation/termination/engine-teardown queue, binding, heap, and RSS proof. No new runtime fix is attributed without a reduced current-head failure.
Parent worker/MessagePort issue: #81.
Related integration: #265 and PR #281.
Overlapping owner: PR #245 (
webscene_v8_runtime_clone.inc).Proven gap
A started
MessagePortwith an installedonmessagehandler can be reclaimed while JavaScript is awaiting the next message. This drops the endpoint and leaves the pending Promise unresolved. The failure becomes deterministic when the current merged CSS/runtime head is combined with the #281 Service Worker Clients tranche.The unchanged focused loop creates a channel, assigns the Promise resolver directly, starts
port1, transfersport2into the Service Worker, and awaits the Service Worker message plus port reply. The old #281 build completes 100/100 cycles. Against currentmainplus #281, it repeatedly stalls near cycle 69–70 withcomplete:false,error:null, and no queue-capacity phase reached.Applying PR #245 commit
94171a32locally preserves local entangled peers but does not fix this case. Replacingchannel.port1.onmessage = resolvewith a closure that records state and callsresolve(event)makes five consecutive 100-cycle runs pass (p95 0.065–0.147 ms). That timing-sensitive difference indicates that the weak native wrapper is not retained by the started-port/listener reachability contract; the closure happens to keep additional JavaScript state alive.This is a generic MessagePort lifetime defect. Do not special-case Service Workers or Promise resolver functions. The implementation path overlaps #245 and must be coordinated there instead of duplicated.
Acceptance
port.onmessage = promiseResolveandaddEventListener('message', listener)afterstart().close(), transfer, listener removal plus inactive state, navigation, worker termination, and engine teardown release native endpoints, weak handles, listeners, queue bytes, and wake registrations.mainwithout adding timing sleeps or artificial JavaScript captures.Proposed fix
Extend the MessagePort reachability model in the existing #245-owned clone/runtime path: retain active ports through the browser-spec active-port condition while they are started and have message listeners, and release that retention at the exact close/transfer/listener/lifecycle boundaries. Preserve weak collection for inactive unreachable pairs.