Skip to content

Keep active MessagePort listeners reachable across garbage collection #288

Description

@wieslawsoltes

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.
  • The original Add service worker client messaging and navigation lifecycle #281 100-cycle Service Worker client gate passes repeatedly on current main without adding timing sleeps or artificial JavaScript captures.
  • 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingvscode-oss/plannedPlanned for the AppScene/WebScene VS Code OSS integration

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions