Repository navigation
bake: deref html_bundle in DevServer Drop #32213
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1247,6 +1247,10 @@ | |
| // SAFETY: stored ref from `init_from_any_blob`; no live borrow. | ||
| unsafe { StaticRoute::deref_(cached.as_ptr()) }; | ||
| } | ||
| // SAFETY: paired with the `ref_` taken in | ||
| // `get_or_put_route_bundle` when this bundle was created; the | ||
| // raw `html_bundle` field has no Drop, so release it here. | ||
| unsafe { bun_ptr::RefCount::<HTMLBundleRoute>::deref(html.html_bundle) }; | ||
|
Check warning on line 1253 in src/runtime/bake/DevServer.rs
|
||
|
Comment on lines
+1250
to
+1253
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 nit: per CLAUDE.md ("Landing PRs" → tests), leak fixes are expected to ship with a regression test in the same PR — either an ASan repro that fails on the unfixed build or a refcount-baseline check. Extended reasoning...What this isThis is a process/convention finding, not a code defect. The deref added in Why it appliesCLAUDE.md:259 states verbatim:
The section header at CLAUDE.md:247-249 notes "everything here has blocked merges". This PR is squarely in the named category — it fixes an intrusive-refcount leak (one Step-by-step: why a test is feasible here
Note that Why this might reasonably be waivedOne verifier objected that this is generic "missing test" review commentary rather than a code bug, and that standing up a DevServer-with-HTML-routes harness from JS test code is a significant undertaking relative to a one-line Drop fix. That's a fair point — the fix is obviously correct by inspection (the Suggested actionEither add a small regression test asserting the |
||
| } | ||
| } | ||
|
|
||
|
|
@@ -5376,8 +5380,8 @@ | |
| let file = &mut self.client_graph.bundled_files.values_mut() | ||
| [incremental_graph_index.get() as usize]; | ||
| file.html_route_bundle_index = Some(bundle_index); | ||
| // Bump the intrusive refcount; matched by | ||
| // `RouteBundle::deinit`'s deref of `html_bundle`. | ||
| // Bump the intrusive refcount; matched by the | ||
| // `html_bundle` deref in `DevServer`'s `Drop`. | ||
| // SAFETY: `html` is a live IntrusiveRc-managed allocation. | ||
| unsafe { bun_ptr::RefCount::<HTMLBundleRoute>::ref_(html) }; | ||
| break 'brk route_bundle::Data::Html(route_bundle::Html { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -164,8 +164,9 @@ | |
| } | ||
| } | ||
|
|
||
| // `deinit` is fully subsumed by Drop: | ||
| // - client_bundle / cached_response: Option<Arc<StaticRoute>> drop = .deref() | ||
| // - Framework: StrongOptional fields drop = .deinit() | ||
| // - Html: bundled_html_text Box<[u8]> drop = allocator.free() | ||
| // html_bundle RefPtr drop = .deref() | ||
| // Zig `RouteBundle.deinit` equivalent, split across two mechanisms: | ||
| // - Drop: `Framework` StrongOptional fields (= .deinit()) and | ||
| // `Html.bundled_html_text` Box<[u8]> (= allocator.free()). | ||
| // - `DevServer`'s `Drop` (explicit): `client_bundle`, `Html.cached_response` | ||
| // (BackRef, no Drop) and `Html.html_bundle` (raw ptr, no Drop) each hold | ||
| // an intrusive ref that is deref'd there. | ||
|
Check failure on line 172 in src/runtime/bake/dev_server/route_bundle.rs
|
||
|
Comment on lines
+167
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 This trailer now correctly documents that Extended reasoning...What the bug isThis PR rewrites the The two leak sitesSite A — DevServer.rs:4578-4582 ( route_bundle::Data::Html(html) => {
if let Some(blob) = html.cached_response.take() {
// Arc<StaticRoute> drop = .deref()
let _ = blob;
}
}
Site B — DevServer.rs:4226-4235 (HTML chunk re-emit): if route_bundle.data.html_mut().cached_response.take().is_some() {
route_bundle.invalidate_client_bundle(unsafe { &mut (*dev_ptr).source_maps });
}
Why existing code doesn't prevent itThe slot is ref-owning. Step-by-step proof (Site A)
Site B is identical except step 3 happens at DevServer.rs:4226-4231 and step 4 is Impact and fixOne // Site A
if let Some(blob) = html.cached_response.take() {
// SAFETY: stored ref from `init_from_any_blob`; no live borrow.
unsafe { StaticRoute::deref_(blob.as_ptr()) };
}
// Site B — drop the .take(), let invalidate_client_bundle handle it
if route_bundle.data.html().cached_response.is_some() {
route_bundle.invalidate_client_bundle(unsafe { &mut (*dev_ptr).source_maps });
}These leaks pre-date this PR, but the PR is explicitly an intrusive-ref-leak sweep on
Comment on lines
+167
to
+172
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 While you're here updating the Extended reasoning...What's stale
Both the stated reason and the TODO predicate are false today. Proof that
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Clear
HTMLBundleRoute.dev_server_idbefore dropping the intrusive ref.get_or_put_route_bundle()trustsHTMLBundleRoute.dev_server_idbefore it touchesself.route_bundles, butDroponly decrements the intrusive ref. If theHTMLBundleRoutesurvives thisDevServer, that external cache can still point at a stale bundle index. Reset the back-reference beforederef()so teardown leaves the surviving route object in a reusable state.Suggested fix
if let route_bundle::Data::Html(html) = &mut rb.data { if let Some(cached) = html.cached_response.take() { // SAFETY: stored ref from `init_from_any_blob`; no live borrow. unsafe { StaticRoute::deref_(cached.as_ptr()) }; } + // SAFETY: `html_bundle` is still alive under this route bundle's intrusive ref. + unsafe { (*html.html_bundle).dev_server_id.set(None) }; // SAFETY: paired with the `ref_` taken in // `get_or_put_route_bundle` when this bundle was created; the // raw `html_bundle` field has no Drop, so release it here. unsafe { bun_ptr::RefCount::<HTMLBundleRoute>::deref(html.html_bundle) }; }As per coding guidelines, "Store state on the object whose lifetime matches it" and "Update every lifecycle method (reset/init/drop/clone) when adding mutable state."
Also applies to: 5383-5386
🤖 Prompt for AI Agents
Source: Coding guidelines