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
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.
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.