fix(client): reject message port link calls after the port closes - #2082
Merged
Merged
Conversation
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.
More templates
@orpc/ai-sdk
@orpc/arktype
@orpc/bun
@orpc/client
@orpc/cloudflare
@orpc/contract
@orpc/experimental-effect
@orpc/evlog
@orpc/hibernation
@orpc/json-schema
@orpc/experimental-lock
@orpc/experimental-msw
@orpc/nest
@orpc/next
@orpc/node
@orpc/openapi
@orpc/opentelemetry
@orpc/pinia-colada
@orpc/pino
@orpc/publisher
@orpc/ratelimit
@orpc/server
@orpc/shared
@orpc/swr
@orpc/tanstack-query
@orpc/trpc
@orpc/valibot
@orpc/zod
commit: |
Contributor
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Contributor
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Reject calls after port close —
MessagePortLinkTransportnow records anAbortError('MessagePort closed')when the port'sclose/disconnectevent fires and throws it from theClientPeersend 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 withMessagePort closedrather than the genericPeer was closed, matching the WebSocket transport. - Tests — new
rejects calls made after closeasserts the exact error and thatpostMessageis 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, andtsc -b packages/clientall 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.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Calls made through the message port
RPCLinkafter the port closes now reject withAbortError('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
MessagePort, Nodeworker_threadsports, and ElectronMessagePortMainsilently droppostMessageonce 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.postMessagethrows on a disconnected port in both Chrome and Firefox. A remote disconnect now surfaces the sameAbortErroras the other port kinds.Testing
AbortError('MessagePort closed')and never posts a message. It timed out before the fix.postMessagedrops messages without throwing andclosefires on both ends. Electron, Chrome, and Firefox behavior was checked against their source.A client that calls
close()on its own browserMessagePortcan still hang, because the HTML spec firescloseonly at the other end of the channel. Node and Electron fire it on both ends.