Refcounted types own their teardown: impl Drop instead of destroy hooks, RefPtr instead of raw holders, rc classes

Merged
DY
2mo ago
+1656-3390

What does this PR do?

Follow-up to #40478 (RefPtr<T> owns its ref). Now that an intrusive ref can be held as a value, the places that still kept one as a raw *mut T + hand-written ref_()/deref(), and the refcounted types that still tore themselves down through a destroy = fn that ended in heap::take(this), move to plain ownership. No behaviour change intended; per path the work done is the same as on main (a RefPtr is a NonNull whose Drop is the deref() that was there before).

  • impl Drop instead of #[ref_count(destroy = …)] / hand-written destructors for StatWatcher/Scheduler, NativeZlib/Brotli, JSMySQLConnection, PostgresSQLConnection, FileSink, FetchTasklet, LinuxMemFdAllocator, Element, Terminal, PipeReader, JSBundleCompletionTask, JSValkeyClient, dns Resolver, NewSocket, Timeout/Immediate, NodeHTTPResponse, H2FrameParser; CronJob, AttributeIterator, RefData, JSMySQLQuery just lose a redundant override. The derive's default (drop the Box) frees the allocation, so error paths and by-value owners get the same teardown. WindowsNamedPipeContext (async free), H2FrameParser (pooled slot) and Response (weak-ref aware) keep an override. NewSocket, timers, Resolver, JSValkeyClient, Response, ServePlugins, S3UploadStreamWrapper, NodeHTTPResponse get a derive instead of a hand-rolled count.
  • .classes.ts refCounted: true for the 26 class definitions (31 generated classes) whose payload is refcounted: the generated finalize thunk runs an optional fn finalize(&self) hook and then drops the wrapper's ref, instead of handing the type a Box<Self> it does not own. 11 finalize bodies disappear and the rest lose their heap::release/finalize_js_box preamble; 10 more non-refcounted finalize bodies that only dropped the Box (some after JsRef::finalize, which drop does) fall back to JsFinalize's default; SecureContext's moves to owning its SSL_CTX.
  • Raw refcounted holders → RefPtr / ProcessHandle / AbortSignalRef / HeadersRef / OwnedSslCtx: SQL request queues (VecDeque<RefPtr<Query>>) and prepared-statement maps; HTTP-thread SSL context cache / proxy-deref queue / h2 session registry; FetchTasklet's stream buffer, native Response and abort signal; FileSink::init/create (returns RefPtr; ~15 manual derefs in Blob go); every *mut Process holder (shell, lifecycle scripts, security scanner, bun test --parallel, --filter/multi-run, webview, Subprocess; to_process() returns RefPtr<Process>, ProcessHandle derefs to Process); IPC SendQueue; S3 MultiPartUpload holders and Store::S3.credentials (shared instead of deep-duped); HTMLRewriter suspended wrappers / attribute iterators / Response refs; valkey deferred-close task and timer keep-alive; h2 cork slot; WebWorker construction + thread refs (RefPtr::new, into_raw() to C++); ServePlugins; AbortSignal::ref_() returns AbortSignalRef. The h2 parser's socket attachment (install as native callback, or hold a socket ref when that slot is taken) is one owner type, NativeSocket, undone on detach/Drop. Every owned SSL_CTX reference (create_ssl_context, SSLContextCache::get_or_create*, SecureContext.ctx (callers clone it; borrow() is gone), SSLWrapper, Listener, HTTPContext, DuplexUpgradeContext, named-pipe TLS, valkey, quic, SQL connections, RareData.default_client_ssl_ctx) is an OwnedSslCtx.
  • Scope keep-alives (x.ref_(); …re-entrant…; x.deref()) become a RefPtr local in NodeHTTPResponse, zlib, valkey, dns, Postgres, Terminal, memfd, S3 upload stream. bun_ptr::finalize_js_box{,_noop}, destroy_box_with and CellRefCounted::ref_nn go away with their last callers.

Left as-is (refs owned by FFI/uws ext slots, lock-free queues, deliberately Copy snapshots, or cross-callback ownership tokens): RequestContext (pool-allocated), Blob / ReadableStream::Source (their own count types), WindowsNamedPipeContext::SocketType, StatWatcherScheduler's intrusive queue, ProcessAutoKiller's pointer-keyed map, Process poll/waiter-thread refs, SyncWindowsProcess.process, BlockList structured-clone refs, the auto-flusher/promise/pending-write flags in NodeHTTPResponse/FileSink/StaticPipeWriter, and the timer internals' ref_()/deref() pins (their container is found by container_of).

Bugs fixed (error paths only, because the ref is now a value that drops): a Postgres query reusing a named statement whose prepare had failed leaked a statement ref and a query ref if converting that error to JS threw; a bun test --parallel worker whose spawn/watch failed leaked its Process; an h2 parser re-corked by re-entrant JS during a forced uncork no longer strands the cork slot's ref.

How did you verify your code works?

cargo check/clippy on linux + windows targets, source lints. Debug build + the suites touching each area: serve/serve-file, node-http/http2/tls, net/socket, fetch + fetch.stream/tls, spawn/child_process/shell, sql/sqlite/mysql, zlib, fs.watch/watchFile, timers, dns, valkey, S3, HTMLRewriter, image, cron, terminal, workers — passing (two pre-existing local debug-build failures, worker.test.ts message-flood and fs.watch inotify-overflow, fail identically on an unmodified build of the base commit).