Skip to content

fix(client): reject message port link calls after the port closes - #2082

Merged
dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/hopeful-benz-efe7b5
Sep 25, 2026
Merged

dinwwwh merged 1 commit into
middleapi:mainfrom
dinwwwh:claude/hopeful-benz-efe7b5

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Sep 25, 2026

Copy link
Copy Markdown
Member

Calls made through the message port RPCLink after the port closes now reject with AbortError('MessagePort closed') instead of hanging forever. In-flight calls cut off by the close now reject with the same reason instead of the generic "Peer was closed". This is the message port counterpart of #2081.

Fixes

  • Web MessagePort, Node worker_threads ports, and Electron MessagePortMain silently drop postMessage once closed, so any later call stayed pending unless the caller passed an abort signal. Those calls now reject right away and nothing is posted to the dead port.
  • Browser extension ports already rejected, because postMessage throws on a disconnected port in both Chrome and Firefox. A remote disconnect now surfaces the same AbortError as the other port kinds.

Testing

  • New test: a call made after close rejects with AbortError('MessagePort closed') and never posts a message. It timed out before the fix.
  • The existing close test now asserts the exact abort reason.
  • Checked Node 24 directly: after close, postMessage drops messages without throwing and close fires on both ends. Electron, Chrome, and Firefox behavior was checked against their source.

A client that calls close() on its own browser MessagePort can still hang, because the HTML spec fires close only at the other end of the channel. Node and Electron fire it on both ends.

Once a MessagePort, Electron MessagePortMain, or Node worker port closes,
postMessage silently drops data, so calls made afterwards never settled
unless the caller passed an abort signal. Store the close reason and throw
it from the peer send callback, so later calls reject with
AbortError('MessagePort closed'), the same reason given to in-flight calls.
@pkg-pr-new

pkg-pr-new Bot commented Sep 25, 2026

Copy link
Copy Markdown
More templates

@orpc/ai-sdk

npm i https://pkg.pr.new/@orpc/ai-sdk@2082

@orpc/arktype

npm i https://pkg.pr.new/@orpc/arktype@2082

@orpc/bun

npm i https://pkg.pr.new/@orpc/bun@2082

@orpc/client

npm i https://pkg.pr.new/@orpc/client@2082

@orpc/cloudflare

npm i https://pkg.pr.new/@orpc/cloudflare@2082

@orpc/contract

npm i https://pkg.pr.new/@orpc/contract@2082

@orpc/experimental-effect

npm i https://pkg.pr.new/@orpc/experimental-effect@2082

@orpc/evlog

npm i https://pkg.pr.new/@orpc/evlog@2082

@orpc/hibernation

npm i https://pkg.pr.new/@orpc/hibernation@2082

@orpc/json-schema

npm i https://pkg.pr.new/@orpc/json-schema@2082

@orpc/experimental-lock

npm i https://pkg.pr.new/@orpc/experimental-lock@2082

@orpc/experimental-msw

npm i https://pkg.pr.new/@orpc/experimental-msw@2082

@orpc/nest

npm i https://pkg.pr.new/@orpc/nest@2082

@orpc/next

npm i https://pkg.pr.new/@orpc/next@2082

@orpc/node

npm i https://pkg.pr.new/@orpc/node@2082

@orpc/openapi

npm i https://pkg.pr.new/@orpc/openapi@2082

@orpc/opentelemetry

npm i https://pkg.pr.new/@orpc/opentelemetry@2082

@orpc/pinia-colada

npm i https://pkg.pr.new/@orpc/pinia-colada@2082

@orpc/pino

npm i https://pkg.pr.new/@orpc/pino@2082

@orpc/publisher

npm i https://pkg.pr.new/@orpc/publisher@2082

@orpc/ratelimit

npm i https://pkg.pr.new/@orpc/ratelimit@2082

@orpc/server

npm i https://pkg.pr.new/@orpc/server@2082

@orpc/shared

npm i https://pkg.pr.new/@orpc/shared@2082

@orpc/swr

npm i https://pkg.pr.new/@orpc/swr@2082

@orpc/tanstack-query

npm i https://pkg.pr.new/@orpc/tanstack-query@2082

@orpc/trpc

npm i https://pkg.pr.new/@orpc/trpc@2082

@orpc/valibot

npm i https://pkg.pr.new/@orpc/valibot@2082

@orpc/zod

npm i https://pkg.pr.new/@orpc/zod@2082

commit: 6465c5f

@codspeed

codspeed Bot commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 30 untouched benchmarks


Comparing dinwwwh:claude/hopeful-benz-efe7b5 (6465c5f) with main (29285f6)

Open in CodSpeed

@codecov

codecov Bot commented Sep 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • Reject calls after port close — MessagePortLinkTransport now records an AbortError('MessagePort closed') when the port's close/disconnect event fires and throws it from the ClientPeer send callback, so post-close calls settle immediately instead of posting to a dead port and hanging.
  • In-flight reason — the close handler passes that reason into peer.close(reason), so in-flight requests are cut off with MessagePort closed rather than the generic Peer was closed, matching the WebSocket transport.
  • Tests — new rejects calls made after close asserts the exact error and that postMessage is never called; the existing pending-request test now pins the exact reason. I verified the new test hangs (times out) with the guard removed, and that the full message-port suite (19 tests), eslint, and tsc -b packages/client all pass.

Known limitation carried forward (already documented in the PR body): a browser MessagePort closed by the client itself never fires a local close event, so that specific path can still hang; Node/Electron fire on both ends.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@dinwwwh
dinwwwh merged commit 0caff28 into middleapi:main Sep 25, 2026
11 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

Development

Successfully merging this pull request may close these issues.

1 participant