bun_ptr: RefPtr releases on Drop; remove ScopedRef/IntrusiveRc/DestructorCtx

Merged
DY
2mo ago
+1047-2457

What does this PR do?

RefPtr<T> (bun_ptr's owned handle to an intrusively refcounted T) previously had no Drop, so every holder released its ref by hand — .deref() inside Drop impls, take-then-deref at every early return, and a dozen one-off guard newtypes (StoreRef, ScopedRef, StaticRouteRef, FileSinkRef, DerefOnDrop, …) whose only job was to add the missing Drop. Forgetting one leaked; doing it twice was a use-after-free. This makes RefPtr behave like Arc: dropping releases the ref, Clone takes one, into_raw/from_raw move a ref across raw-pointer/FFI boundaries, and it is Send/Sync when T is. No runtime cost change — the same decrement happens at the same points, and RefPtr is now a #[repr(transparent)] NonNull<T> in every profile (the debug-only per-ref tracking it carried was write-only and is deleted; the refcount's use-after-destroy canary stays).

On top of that:

  • One owning-ref type with one API (new/init_ref/from_this/from_raw/into_raw/this_ptr/into_this_ptr/as_ptr/as_non_null). ScopedRef, the IntrusiveRc alias, ThisPtr::ref_guard, dupe_ref, data(), and the hand-rolled newtypes above (StoreRef, StaticRouteRef, FileSinkRef/FileSinkPtr, DerefOnDrop, ResolverRefGuard, Scheduler/WatcherRefGuard, RefDataPtr) are folded into it; HTTPContext.h2_session, h3 PendingConnect.session, subprocess Writable::Pipe, and h2 SignalRef.parser hold a RefPtr instead of a raw pointer + manual deref. Types that take a keep-alive ref on &self all spell it self.ref_guard().
  • The unused DestructorCtx associated type is removed from the refcount traits.
  • WeakPtr releases on Drop (its manual deref() is gone).
  • JsCell::set writes first and drops the old value after (matching Cell::set), so a field whose Drop re-enters or frees the owner is sound.
  • ~60 manual .deref() sites, AnyRoute::deref_, and several Drop/finalize bodies that existed only to release a ref are deleted.
  • Pre-existing, fixed while here: the install security scanner over-released its JSON pipe writer when start() failed (use-after-free on that error path), and on Windows its errdefer closed the uv pipe a second time after the writer already owned it.
  • s3.test.ts: fix a racy received += await … accumulator that under-counted when two part uploads overlapped.

First step of replacing manual deinit/deref teardown with ownership + Drop across the Rust tree; converting the remaining raw *mut T holders of refcounted types to RefPtr follows separately.

How did you verify your code works?

cargo check --workspace + clippy for linux-gnu/musl, windows, darwin, freebsd, android. Debug build + targeted suites covering every touched holder: blob/Bun.file/Bun.write, serve static/file/html/routes + reload, body-leak, WebSocket client (incl. proxy tunnel), fetch keep-alive/http2/proxy/SSL-ctx eviction, spawn stdin/stdout, shell, FileSink, cron, dns, fs.watchFile, TLS upgrade/duplex, node http2, S3 multipart, archive, security scanner, bake dev server, bun:test done-callback/expect — all pass.