Skip to content

fix(ext/http): don't panic recycling cancelled record on native response#36053

Merged
bartlomieju merged 1 commit into
mainfrom
fix/http2-recycle-cancelled-native-response
Jul 15, 2026
Merged

fix(ext/http): don't panic recycling cancelled record on native response#36053
bartlomieju merged 1 commit into
mainfrom
fix/http2-recycle-cancelled-native-response

Conversation

@bartlomieju

Copy link
Copy Markdown
Member

Fixes #36046, a panic in HttpRecord::recycle reported on Windows over
HTTP/2. It triggers when a client cancels a request while its handler is
still running and the handler then returns a native fast-path response
(such as new Response()). Depending on timing it surfaced as either
HTTP state error: Expected to be last strong reference or a RefCell
borrow error at ext/http/service.rs.

recycle() pools the record's allocation and takes the inner value, so it
requires sole ownership of the Rc<HttpRecord>. Every correct caller of
complete() moves the last reference in (for example set_response owns
http: Rc<HttpRecord> and calls http.complete()). The native-response
path added in #34446 routes through the HttpRecordExternal enum, whose
complete method took &self and therefore had to clone the Rc. That
clone left the enum's own reference alive, so on the cancelled
(been_dropped) path complete() reached finish and recycle while an
extra reference was still outstanding, violating the invariant.

The fix makes HttpRecordExternal::complete consume self, moving the
record into HttpRecord::complete rather than cloning it. All call sites
were already using the value for the last time, so no other changes were
needed.

The added unit test reproduces the crash: it cancels HTTP/2 streams (over
TLS, which is where the hyper record path is used) while the handler is
still pending, then confirms the server stays healthy. It panics the
pre-fix binary and passes after the change.

When an HTTP/2 request is cancelled while its handler is still running and
the handler then returns a native fast-path response (e.g. `new Response()`),
Deno could panic in `HttpRecord::recycle` with either "Expected to be last
strong reference" or a `RefCell` borrow error.

`recycle()` pools the record allocation and requires sole ownership of the
`Rc<HttpRecord>`. Every correct caller of `complete()` moves the last
reference in, but `HttpRecordExternal::complete` took `&self` and had to
clone the `Rc`, leaving the enum's own reference alive. On the cancelled
(`been_dropped`) path `complete()` recycles the record while that extra
reference is still outstanding, breaking the invariant.

Make `HttpRecordExternal::complete` consume `self` so the record is moved
into `HttpRecord::complete` instead of cloned. All call sites were already
last-use.

Closes #36046
@bartlomieju
bartlomieju force-pushed the fix/http2-recycle-cancelled-native-response branch from 9b96179 to 61fdf92 Compare July 15, 2026 10:03
@bartlomieju
bartlomieju merged commit 4c72e73 into main Jul 15, 2026
136 checks passed
@bartlomieju
bartlomieju deleted the fix/http2-recycle-cancelled-native-response branch July 15, 2026 11:22
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.

Deno has panicked, PC, Windows 11

1 participant