Skip to content

fix: release call arguments once a forwarded or queued call is sent - #294

Open
ndisidore wants to merge 1 commit into
mainfrom
fix/disposal-bugs
Open

ndisidore wants to merge 1 commit into
mainfrom
fix/disposal-bugs

Conversation

@ndisidore

Copy link
Copy Markdown
Member

Stubs passed as call arguments were never released when the call was re-sent from an owned payload: a call forwarded to a remote stub (three parties, or a .map() callback), or one made through a pending RpcPromise. The serializer only dups the payload's stubs or cites their import IDs, and sendCall() never disposed the payload on success. An RpcTarget passed this way never had its disposer run, and its table entries lived as long as the session. #241 covered the failure paths; this is the success path.

Fixes #293, which the new dispose would otherwise hit on a successful forwarded call.

  • Dispose args after send(), not after serializing. Disposing can release an import the message cites; done earlier, the peer aborts with no such entry on exports table.
  • Does nothing for app params, which don't own their stubs. Only wire-parsed and deep-copied payloads change behavior.
  • No-op catch on activePull, as Unhandled rejection when a call fails before delivery and an argument is an imported promise #293 suggests. An already-pulling import disposed before anyone pulls rejected a promise nobody observed. Real waiters still see the rejection.
  • Same line in sendStream() to honor the stream() ownership contract. No public path delivers an owned payload there today, so no test.
  • Tests go through the public API: stub-to-stub forwarding across three parties, a pipelined argument (guards the placement), a caller-exported promise, and a call through new RpcPromise(promise).
  • The two unhandled-rejection tests rely on vitest failing the run, like the existing one next to them. Node only; workerd doesn't surface it.
  • Ran the Node and workerd projects locally. Browser projects not run locally.

When a call's arguments are an owned payload, the serializer only dups
the payload's stubs or cites their import IDs, and sendCall() never
disposed the payload on success. That is the case for a call forwarded
to a remote stub (three parties, or a .map() callback) and for a call
made through a pending RpcPromise. An RpcTarget passed this way never
had its disposer run, and its table entries lived as long as the
session. #241 covered the failure paths; this is the success path.

Dispose the payload after the message is sent. Disposing can release an
import the message cites, so doing it earlier makes the peer abort with
"no such entry on exports table". App params don't own their stubs, so
for them this does nothing. sendStream() gets the same line to honor
the stream() ownership contract, though no public path delivers an
owned payload there today.

Disposing forwarded arguments can dispose an imported promise nobody
has pulled yet, which rejected an unobserved promise (#293). Give
activePull a no-op catch; real waiters still see the rejection.
@changeset-bot

changeset-bot Bot commented Nov 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c615b65

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
capnweb Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Nov 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/cloudflare/capnweb@294
npm i https://pkg.pr.new/cloudflare/capnweb/capnweb-validate@294

commit: c615b65

@clanky-the-clanker clanky-the-clanker Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Approved — No issues found. Arguments are now disposed after send(), which releases owned payloads and leaves app-supplied params unchanged.

The decision is advisory: this review is a comment and never approves, blocks or requests changes on the pull request.


✅ All checks passed

🚀 Release: ✅ LGTM

Risk: Low. The changeset entries match the fix.


🔒 Security: ✅ LGTM

No issues found.


📖 AGENTS.md: ✅ LGTM

No issues found.


📋 Code Quality: ✅ LGTM

No issues found.


⚡ Performance: ✅ LGTM

No issues found.


📝 Documentation: ✅ LGTM

No issues found.


Reviewed commit: c615b65e

@cloudflare-workers-and-pages

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://fix-disposal-bugs.pr.capnweb.com (commit c615b65)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://831a7d7c.pr.capnweb.com c615b65 2026-11-09T14:33:59.175Z Visit the dashboard ↗

This branch has not been deployed

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

Unhandled rejection when a call fails before delivery and an argument is an imported promise

1 participant