workerd: Convert continuation captures to weak handles by danlapid · Pull Request #7817 · cloudflare/workerd · GitHub
Skip to content

workerd: Convert continuation captures to weak handles - #7817

Open
danlapid wants to merge 2 commits into
mainfrom
dlapid/ucc-weak-handles
Open

danlapid wants to merge 2 commits into
mainfrom
dlapid/ucc-weak-handles

Conversation

@danlapid

Copy link
Copy Markdown
Collaborator

Captures in continuations that held bare references or this now hold weak handles, dereferenced with KJ_REQUIRE_NONNULL where they are used, so a use-after-free becomes an exception.
Owners, destruction points and lifetimes are unchanged.
Commit 1 converts captures through accessors that already exist.
Commit 2 adds workerd::WeakOwner, new PtrTarget bases and accessors with the captures that use them, and teaches workerd-unsafe-continuation-capture the KJ parameter-lifetime contract from the style guide.
The check is not enabled everywhere yet.

Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com

@danlapid
danlapid requested review from a team as code owners October 19, 2026 00:25
@clanky-the-clanker

clanky-the-clanker Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

@ask-bonk

ask-bonk Bot commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

Since last review: 2 resolved, 0 still open, 0 new.
LGTM!

Not re-run: api-compat, compat-flags, jsg-gc, kj-style, memory-safety, rust-first (no author changes in their files since the last review)


Reviewed commit: 8fe074be · github run

Comment thread tools/clang-tidy/tests/unsafe-continuation-capture-test.c++
Comment thread src/workerd/util/weak-refs-test.c++
@danlapid
danlapid force-pushed the dlapid/ucc-weak-handles branch from 18c2a81 to 23a4c38 Compare October 19, 2026 03:08
@danlapid
danlapid force-pushed the dlapid/ucc-weak-handles branch from 23a4c38 to 64e7425 Compare October 19, 2026 07:24
Comment thread tools/clang-tidy/unsafe-continuation-capture-test.py
@danlapid
danlapid force-pushed the dlapid/ucc-weak-handles branch 2 times, most recently from 3b980ea to ca7a09a Compare October 20, 2026 06:02
Comment thread src/workerd/api/streams/identity-transform-stream.c++

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

clever

Comment thread tools/clang-tidy/unsafe-continuation-capture.c++ Outdated
Comment thread tools/clang-tidy/unsafe-continuation-capture.c++
@danlapid
danlapid force-pushed the dlapid/ucc-weak-handles branch from ca7a09a to 6223b40 Compare October 20, 2026 23:13
Comment thread tools/clang-tidy/unsafe-continuation-capture.c++ Outdated
Comment thread tools/clang-tidy/tests/unsafe-continuation-capture-test.c++
danlapid and others added 2 commits October 21, 2026 15:39
Convert continuation captures of `this` and of references to
existing weak handles, dereferenced with KJ_REQUIRE_NONNULL where
they are used: IoContext::getWeakRef(), JSG_THIS_WEAK, kj::Pin
addWeak(), kj::Rc downgrade(), and addWeakToThis() on classes that
are already kj::PtrTarget or kj::Refcounted. A continuation that
outlives its referent now throws instead of touching freed memory;
otherwise nothing changes.

Also document the caller-keeps-the-buffer-alive contract on
WritableStreamSink::write() and DatagramChannel::send(), and add the
//src/workerd/util dependency and weak-refs.h includes that later
commits use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Changes with no behaviour change that need some focus to review:

- Add workerd::WeakOwner<T>, a heap box whose kj::Weak handles go
  null before its value is destroyed, with tests.
- Add kj::PtrTarget bases and addWeak() accessors to objects that
  continuations refer to (OutputGate, ActorCacheInterface,
  IoContext_IncomingRequest, ActorCache's list streams,
  ChannelTokenHandler, StoredExternalHandler, queue consumers,
  WorkerEntrypoint, JsRpcTargetBase, CapnpServer, and others), and
  convert those continuations' captures to the new handles,
  dereferenced with KJ_REQUIRE_NONNULL at the use. PtrTarget makes
  these classes non-movable; none of them is moved.
- Pass sendAutoResponse() a kj::WeakRc<kj::WebSocket> instead of a
  reference; the caller already holds a kj::Rc.
- ActorCache: move flushImpl()'s post-RPC continuations into
  finishFlush() and finishFlushDeleteAll().
- html-rewriter: fold the evalLater() + wait() into waitForThunk()
  and waitForReplacerThunk(), so the chain is consumed in the frame
  that creates it. Evaluation order is unchanged.
- workerd-unsafe-continuation-capture: treat as safe what the KJ
  style guide guarantees (reference, pointer and view parameters
  captured by the promise their function returns), static storage,
  chains consumed in the same frame (result-owning sinks, local
  TaskSets awaited via onEmpty(), Timer::timeoutAfter,
  Canceler::wrap), PromiseCache factories, and SuspendableRequest
  url/headers owned by the returned HttpService. Recognize kj::Weak,
  kj::WeakRc, workerd::WeakRef and AtomicWeakRef as owning captures.
  Document the safe patterns in async-patterns.md, and run the
  check's fixture in CI against its annotations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@danlapid
danlapid force-pushed the dlapid/ucc-weak-handles branch from 6223b40 to 8fe074b Compare October 21, 2026 15:41
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.

3 participants