jsc: move close_all_socket_groups to VirtualMachine; hot_reloader scopeguard via BackRef - #35376
jsc: move close_all_socket_groups to VirtualMachine; hot_reloader scopeguard via BackRef#35376robobun wants to merge 2 commits into
Conversation
WalkthroughChangesVM socket-group shutdown
Hot-reloader lifetime handling
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 8:03 AM PT - Jul 24th, 2026
❌ @autofix-ci[bot], your commit 542a3c1 has 1 failures in
🧪 To try this PR locally: bunx bun-pr 35376That installs a local version of the PR into your bun-35376 --bun |
|
Found 1 issue this PR may fix:
🤖 Generated with Claude Code |
|
This PR may be a duplicate of:
🤖 Generated with Claude Code |
4ebd0e5 to
1edfcfd
Compare
There was a problem hiding this comment.
Additional findings (outside current diff — PR may have been updated during review):
-
🟡
src/sql_jsc/jsc.rs:326-334— The trait doc comment onVirtualMachineSqlExt::postgres_socket_group(jsc.rs:289-292) still says it "Encapsulates therare_data(&mut self)/*_group(.., &VirtualMachine)borrowck conflict … so the four call sites need no per-site raw-pointer dance." After this PR the*_groupaccessors take*mut uws::Loopand there is no borrowck conflict left to encapsulate — the doc now describes a removed mechanism and should be updated (or dropped) alongside the signature change.Extended reasoning...
What is stale
The trait declaration at
src/sql_jsc/jsc.rs:289-292reads:/// Lazy-init `RareData`'s per-protocol uws [`bun_uws::SocketGroup`]. /// Encapsulates the `rare_data(&mut self)` / `*_group(.., &VirtualMachine)` /// borrowck conflict (the two borrows touch field-disjoint state) so the /// four call sites need no per-site raw-pointer dance. fn postgres_socket_group<const SSL: bool>(&mut self) -> &mut bun_uws::SocketGroup;
Three claims in this doc are now false after the PR:
*_group(.., &VirtualMachine)— the signature is now*_group(.., *mut uws::Loop)(seerare_data.rsin this diff).- "borrowck conflict (the two borrows touch field-disjoint state)" — there is no conflict anymore.
uws_loop()returns aCopyraw pointer that the caller snapshots before callingrare_data(), so the two borrows never overlap. - "raw-pointer dance" — this PR's whole point is to eliminate that dance; the impl body just below (lines 326-334) is now a trivial two-liner with no raw pointers at all.
Why the PR should have updated it
This PR systematically deleted or rewrote every other "reshaped for borrowck" / "raw-pointer split-borrow" comment at the call sites it touched —
websocket_client.rs,WebSocketUpgradeClient.rs,VirtualMachine.rs,js_bun_spawn_bindings.rs,Channel.rs,socket_body.rs,js_valkey.rs, and the impl-body comments in this very file (the old "Route the read-onlyvmargument through the JS-thread singleton accessor…" comment at lines 327-332 was removed). The trait-level doc on the same method is the one instance that was missed.Per REVIEW.md, "Comments carry only durable non-obvious content" and "One source of truth; update every consumer atomically." A doc comment describing a mechanism the PR removes is exactly the kind of stale content that rule targets.
Step-by-step proof
- Before this PR:
RareData::postgres_group<const SSL>(&mut self, vm: &VirtualMachine)— callingvm.rare_data().postgres_group(vm)needed&mut vmand&vmsimultaneously, hence the borrowck workaround the doc describes. - After this PR (
rare_data.rshunk):RareData::postgres_group<const SSL>(&mut self, loop_: *mut uws::Loop). - After this PR (
jsc.rs:326-328hunk): the impl islet loop_ = self.uws_loop(); self.rare_data().postgres_group::<SSL>(loop_)—loop_is aCopyvalue taken while no borrow ofselfis outstanding, thenrare_data()takes&mut selfcleanly. No conflict, no raw pointer, nothing to "encapsulate." - The trait doc at lines 289-292 was not touched by the diff and still names the
&VirtualMachinesignature and the raw-pointer dance.
Impact
Documentation-only. No runtime effect. A future reader following the doc to understand why this trait method exists will be misled into thinking there is still a split-borrow being hidden here.
Fix
Rewrite the doc to describe what the method actually does now, e.g.:
/// Lazy-init `RareData`'s per-protocol uws [`bun_uws::SocketGroup`]. /// Snapshots `self.uws_loop()` before the `rare_data()` `&mut self` borrow /// so callers get a single `&mut self` accessor.
or simply drop the second sentence, since the new accessor block in
rare_data.rsalready documents the snapshot-loop pattern.
|
Rebased onto #35373 (which independently made the identical The stale |
|
No test file accompanies this PR because it is a behavior-preserving refactor with no fail-before. Both changes relocate code to where its invariant already lives, with byte-identical runtime behavior:
A test that exercises these paths (worker teardown with live sockets, (The |
There was a problem hiding this comment.
No issues found — the refactor looks behavior-preserving. Deferring to a human because it touches unsafe VM-teardown ordering and watcher-thread BackRef derefs, and it's stacked on #35373 (merge order + the binary-size CI gate need a maintainer call).
What was reviewed:
close_all_socket_groups: body moved verbatim fromRareData(whereselfwas unused) toVirtualMachine; both call sites keep therare_data.is_some()guard, so no behavior change.hot_reloader::on_file_update: drop order (_flush→flush_evictionsbeforecurrent_task→enqueue) is preserved; the guard now reaches the sameWatcherviaBackRef<Ctx>→bun_watcher_mut()instead of a cached*mut Watcher.src/http/lib.rsandjs_bun_spawn_bindings.rshunks are pure autofix.ci rustfmt — no logic change.
Extended reasoning...
Overview
This PR relocates close_all_socket_groups from RareData (where it took &mut self but did let _ = self;) to VirtualMachine as a &self method, and rewrites the hot_reloader::on_file_update scopeguard to capture the reloader's BackRef<Ctx> (Copy) instead of a raw *mut Watcher. Two call sites (VirtualMachine::global_exit, WebWorker::shutdown) drop their unsafe { &*core::ptr::from_ref(self) } split-borrow workaround. The src/http/lib.rs and js_bun_spawn_bindings.rs hunks are autofix.ci rustfmt reflows with zero semantic change.
Security risks
None identified. No user-input handling, no auth/crypto, no new FFI surface. The change removes unsafe raw-pointer reborrows rather than adding them.
Level of scrutiny
High — this is unsafe Rust in two memory-safety-sensitive paths: (1) VM/worker teardown ordering (close_all_socket_groups runs on_close JS callbacks and must precede JSC teardown), and (2) the file-watcher thread's scopeguard drop order (which the existing comments document as load-bearing for an EBADF race). REVIEW.md flags native memory safety as the most-blocked category, and "before deleting odd-looking code, git-blame why it was written" applies directly to the removed raw-pointer laundering. I traced both changes and they are behavior-preserving: the moved method body is byte-for-byte equivalent (same 8-round loop, same close_all_groups/drain_closed_sockets sequence), and the hot_reloader guard reaches the identical Watcher instance via the already-audited bun_watcher_mut() path with the same LIFO drop order.
Other factors
The PR is stacked on #35373; the autofix.ci reformats and the failing binary-size check (+513KB linux) almost certainly belong to the base branch, not this ~20-line net change — but that's a merge-order/[skip size check] decision a maintainer should make. Tests (hot.test.ts, worker.test.ts, watch.test.ts) and rust:check-all on 10 targets are reported passing. Given the domain sensitivity and the stacked-PR logistics, I'm deferring rather than approving.
|
CI on build 79293: neither failure is from this diff. binary-size (+513 KB linux): baseline drift. This build's proxy-stress-errors.test.ts (debian 13 x64, 1 lane): the same run reported The diff is ready for review; no retrigger pushed since the binary-size check is deterministic and would fail again until the base branch rebases. |
|
Fix conflicts |
08ce59d to
c05ecbc
Compare
|
Rebased onto the current tip of #35373 ( |
…peguard via BackRef Stacked on #35373, which retargets the RareData *_group accessors to take *mut uws::Loop. This PR finishes the rare_data/field-alias borrowck cleanup cluster (three sites #35373 doesn't cover). close_all_socket_groups was a &mut self method on RareData that didn't use self (let _ = self;): it walks the per-VM uSockets loop's linked group list, not RareData's embedded fields. Move it to VirtualMachine as a &self method so global_exit and WebWorker::shutdown call it directly instead of re-deriving &VirtualMachine through core::ptr::from_ref(self) while holding &mut rare_data. hot_reloader::on_file_update held the Watcher as a *mut so self could be reborrowed in the loop body and the flush_evictions scopeguard didn't pin a &mut across it. The reloader already stores a BackRef<Ctx> (Copy); have the scopeguard capture that and reach the Watcher on drop, and reborrow via self.get_context() at the two remove_at_index sites. Same BACKREF unsafe obligation, one audited site instead of three.
There was a problem hiding this comment.
No bugs found. The 4-file src/jsc/ change this PR describes (moving close_all_socket_groups to VirtualMachine, and the hot_reloader scopeguard capturing BackRef<Ctx> instead of a cached *mut Watcher) looks correct and behavior-preserving. Deferring because the diff against main also carries the stacked #35373 changes — notably the src/http/lib.rs restructuring of handle_on_data_headers / removal of pending_response / clone_metadata taking &Response — which is a substantial HTTP-client hot-path refactor that should get human eyes (ideally on #35373 itself).
What was reviewed:
close_all_socket_groups: body is byte-identical to the removedRareDatamethod; both call sites keep therare_data.is_some()guard.hot_reloader::on_file_update:self.get_context()and the guard'sctx_ref.get_mut().bun_watcher_mut()reach the sameWatcheras the old cached*mut; drop order (_flushbeforecurrent_task) is preserved.- Checked that
self.get_context()reborrows don't conflict with thewatchlist-derived slices (they're a parameter, not fromself).
Extended reasoning...
Overview
This is a stacked PR on top of #35373. The PR's own contribution is 4 files in src/jsc/ (+51/-69): moving close_all_socket_groups from RareData (where it began with let _ = self;) to VirtualMachine as a &self method, and reworking the hot_reloader::on_file_update scopeguard to capture the BackRef<Ctx> (which is Copy) instead of a cached *mut Watcher.
However, because the PR base is main, the diff GitHub shows (and that the bug-hunting system reviewed) is 19 files including all of #35373's changes: the RareData::*_group signature retarget across ~10 call sites, plus a substantial src/http/lib.rs refactor that removes InternalState::pending_response, changes clone_metadata to take &picohttp::Response<'_> by reference, restructures handle_on_data_headers around a moved-out response_message_buffer local, changes to_result() to return HTTPClientResult<'static> with body: None, and simplifies the chunked-decode path to always copy into scratch. New tests in fetch-proxy-connect-tunnel-split-envelope.test.ts pin the split-read accumulation behavior.
Security risks
None identified. This is internal refactoring of borrow patterns and buffer ownership in the HTTP client and VM teardown paths; no user-facing input validation or auth surface changes.
Level of scrutiny
The 4-file src/jsc/ portion is low-risk: it relocates code with byte-identical bodies and swaps one raw-pointer pattern for another that resolves to the same target. I traced both changes and they preserve behavior (same Watcher reached, same drop ordering, same rare_data.is_some() guards).
The stacked src/http/ portion is higher-risk: it touches the fetch client's response-header parsing hot path, changes struct layout (InternalState loses a field), and restructures buffer ownership in handle_on_data_headers. The new tests cover the short-read / 1xx / chunked-in-same-read paths, which is good, but this is exactly the kind of change REVIEW.md flags for careful behavior-preservation audit ("Treat every refactor as guilty until proven behavior-preserving").
Other factors
- Jarred-Sumner is already engaged on the PR (requested conflict resolution).
- The author has documented extensively that the http/ hunks belong to #35373; approving here would merge both, so the http/ changes should be reviewed on #35373 (or here if #35373 merges first and this rebases to a 4-file diff).
- No bugs were found by the bug-hunting system across the full 19-file diff.
- CI status (build 79571) is still building at time of review.
8b615b5 to
d4e578a
Compare
|
#35373 merged; rebased this branch onto current main ( The build 79571 failures were all npm registry outage during the run (every failing test hung at "Resolving dependencies" or got 503/522 from registry.npmjs.org; the registry is responding 200 again now). |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/jsc/VirtualMachine.rs`:
- Around line 984-989: Update the invariant comment near the loop’s linked-group
traversal to remove the transient issue number, build reference, allocation-size
detail, and incident-specific leak wording. Retain only the durable rationale:
Listener/uWS-App groups own separate SocketGroup instances, accepted sockets are
stored there rather than in RareData, and the linked-group list must be
traversed during shutdown.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: e8d17c22-0a93-40d8-94f0-bea8a9f5da83
📒 Files selected for processing (4)
src/jsc/VirtualMachine.rssrc/jsc/hot_reloader.rssrc/jsc/rare_data.rssrc/jsc/web_worker.rs
💤 Files with no reviewable changes (1)
- src/jsc/rare_data.rs
There was a problem hiding this comment.
LGTM — pure relocation refactor removing raw-pointer borrowck workarounds.
What was reviewed:
close_all_socket_groups: body moved verbatim fromRareData(where it beganlet _ = self;) toVirtualMachine; both call sites keep therare_data.is_some()guard, and no other callers exist.hot_reloader::on_file_update: confirmedBackRef<Ctx>isCopyandget_context()reaches the sameWatcheras the removed cached*mut; scopeguard drop order (_flushbeforecurrent_task) is preserved.- Checked
uws_loop_mut()is&selfso the new&selfreceiver works from bothglobal_exit(&mut self) andWebWorker::shutdown.
Extended reasoning...
Overview
Two independent borrowck-cleanup refactors in src/jsc/, follow-up to #35373:
close_all_socket_groupsmoved fromRareData(where it discardedselfwithlet _ = self;and only touchedvm.uws_loop_mut()) toVirtualMachineas a&selfmethod. Both call sites —VirtualMachine::global_exitandWebWorker::shutdown— replace anunsafe { &*core::ptr::from_ref(self) }split-borrow with a directself.close_all_socket_groups()call, keeping theif rare_data.is_some()guard.hot_reloader::on_file_updatereplaces a cached*mut Watcher(held soselfcould be reborrowed in the loop and the_flushscopeguard didn't pin&mut) with the existingBackRef<Ctx>field. The guard now copiesself.ctx(verifiedCopyatsrc/ptr/lib.rs:162) and re-derives theWatcheron drop; the tworemove_at_indexsites reborrow viaself.get_context()inline.
Security risks
None. No user input, network, auth, or crypto surface touched. This is internal lifetime plumbing.
Level of scrutiny
Moderate — VM/worker shutdown and hot-reload eviction are correctness-sensitive paths, but the change is mechanical: the method body is a verbatim move (comments included, as noted in the resolved CodeRabbit thread), and the hot-reloader change substitutes one indirection (*mut Watcher cached at top) for an equivalent one (BackRef<Ctx> → bun_watcher_mut() re-derived per use). Both self.ctx and self.bun_watcher are set once at init and never reassigned, so re-deriving yields the same pointer as caching. The _flush-before-current_task drop order (which prevents the documented EBADF race) is preserved by declaration order.
Other factors
- Grep confirms the two call sites are the only callers of
close_all_socket_groups; no stragglers. uws_loop_mut()takes&self(line 864), so the new&selfreceiver composes cleanly.- The one CodeRabbit inline (comment-history metadata in the moved block) was withdrawn after the move-verbatim rationale was explained; the thread is resolved.
- Jarred's only request ("Fix conflicts") was addressed by rebase; no outstanding human review feedback.
- PR body reports
rust:check-allon all 10 targets and the hot/watch/worker suites passing; the no-new-test rationale (no fail-before for a pure relocation) matches the REVIEW.md exception criteria. - Net -18 lines, three
unsaferaw-pointer reborrows eliminated.
|
Build 79582 (post-rebase onto main): all six test annotations are flaky (passed on retry), none in |
Follow-up to #35373: finishes the
rare_data/field-alias borrowck cleanup (cluster C4 in the audit onfarm/2a32eccb/borrowck-audit) with the threesrc/jsc/sites that did not depend on theRareData::*_groupsignature change.What / Why
close_all_socket_groupsWas a
&mut selfmethod onRareDatathat didn't useself(let _ = self;): it walks the per-VM uSockets loop's linked group list, notRareData's embedded fields. Moved toVirtualMachineas a&selfmethod.global_exitandWebWorker::shutdownnow call it directly:hot_reloader::on_file_updateHeld the
Watcheras a*mutsoselfcould be reborrowed in the loop body and theflush_evictionsscopeguard didn't pin a&mutacross it. The reloader already stores aBackRef<Ctx>(which isCopy); the guard now captures that and re-derives theWatcheron drop, and the tworemove_at_indexsites reborrow viaself.get_context()inline. Sameunsafeobligation (the existing BACKREF invariant), one audited site instead of three.Verification
bun run rust:check-allpasses on all 10 targets.bun bd test test/cli/hot/hot.test.ts test/js/web/workers/worker.test.ts test/cli/watch/watch.test.tsall pass.clippyclean onbun_jsc.This is a behavior-preserving refactor (both changes relocate code to where its invariant lives with byte-identical runtime behavior), so there is no fail-before test; the listed suites are the regression guard.