cloudflare / cloudflare/capnweb
`exportStub()` always allocates a new export ID, even for duplicate capabilities
- Dominant language
- TypeScript
- Stars
- 4k
- Forks
- 143
- Avg merge
- 4d 6h
- Merged PRs (30d)
- 7
Description
`exportStub()` deduplication by hook identity never fires.
https://github.com/cloudflare/capnweb/blob/c2bb17b940b23eb8ab89be1e85538493cb4552ad/src/rpc.ts#L369-L383
`reverseExports` is a `Map`, so dedup is by object identity.
https://github.com/cloudflare/capnweb/blob/c2bb17b940b23eb8ab89be1e85538493cb4552ad/src/serialize.ts#L275-L281
`devaluateHook` routes to either `exportStub` or `exportPromise` depending on whether the type is `"export"`/`"writable"` or `"promise"`. The promise path is intentionally never deduplicated (`exportPromise` comment: "Promises always use a new ID because otherwise the recipient could miss the resolution."), so the issue only concerns the paths that reach `exportStub`:
**`"stub"/"rpc-promise"`** https://github.com/cloudflare/capnweb/blob/c2bb17b940b23eb8ab89be1e85538493cb4552ad/src/serialize.ts#L218-L224 `hook` is set to the result of `hook.dup()` if `pathIfPromise` is falsy (the only time exportStub will be called).
**`"function"/"rpc-target"`** https://github.com/cloudflare/capnweb/blob/c2bb17b940b23eb8ab89be1e85538493cb4552ad/src/serialize.ts#L233-L234
`getHookForRpcTarget()` returns `hook.dup()` for "return" payloads (after correctly deduplicating in `rpcTargets` by target object identity), or a fresh `TargetStubHook.create()` each time for "params" payloads. Either way, a fresh object reaches `exportStub`.
**`"writable"`** https://github.com/cloudflare/capnweb/blob/c2bb17b940b23eb8ab89be1e85538493cb4552ad/src/serialize.ts#L251-L252
`getHookForWritableStream()` uses identical logic to `getHookForRpcTarget()` for our purposes, it deduplicates in `rpcTargets` for "return" payloads but returns `hook.dup()` each time, fresh `WritableStreamStubHook` each time for "params".
In all three cases, `dup()` always returns a new object for every hook type that can be exported (`RpcImportHook`, `TargetStubHook`, `WritableStreamStubHook`, `ReadableStreamStubHook`, `PayloadStubHook`). The two types where `dup()` returns `this` (`ErrorStubHook` and `MapVariableHook`) are never exported.
Because the stub fed to `exportStub` is always a newly created object, it will never be found in `reverseExports`, so we will always create a new export.
This means the same capability appearing twice always gets separate export IDs. This is what prevents the `remoteRefcount` bug (#140) from being triggered currently
Contributor guide
Assessment
This issue has not been assessed yet.