Conversation
|
Status: diff complete and verified locally (cargo check/clippy on all targets, collections unit tests under miri, the crash handler test matrix, and the bundler/install/sourcemap/env/sql suites listed in the description). Waiting on CI. Follow-up PRs will peel the next layer of functions that still return Result<_, AllocError> and then remove handle_oom itself. |
|
@robobun fix conflicts + rebase |
ArrayHashMap, StringArrayHashMap, StringHashMap, StringSet, StringMap, HashMap, MultiArrayList, the bit sets, ByteVecExt, OffsetByteList and PriorityQueue returned Result<_, AllocError> from methods whose bodies grow a Vec or hashbrown table, which abort on allocation failure and never return Err. The raw-allocation containers (MultiArrayList, the dynamic bit sets) now call handle_alloc_error like Vec does. The crash handler installs an alloc error hook so every failed infallible allocation is reported as out-of-memory instead of as an abort() crash. Types whose clone() returned Result now implement Clone. Leaf callers updated; LinearFifo is left fallible because a full StaticBuffer is reported through the same error.
…f memory Adds a crash_handler.allocError() test hook that calls handle_alloc_error, the path every infallible std allocation takes when the allocator returns null. Without the alloc error hook std prints "memory allocation of N bytes failed" and aborts, which is reported as an ordinary crash (or, on ASAN builds, not reported at all).
let_unit_value was allowed because the port left many `let _ = call();` bindings around calls that return (); those are all removed in the next commit. The generated check_slow wrappers for void C++ functions bound their result too, so cppbind now emits a plain call for those. dotenv and http_types no longer use bun_alloc.
Callers of the collection APIs made infallible in the previous commits no longer wrap them in handle_oom, unwrap_or_oom, expect, ?, map_err or let _ =. Thin wrappers whose only error source was one of those calls (bun_dotenv::Map and Loader::load_process, DeclaredSymbolList, StringPool, PathToSourceIndexMap, SavedSourceMap::put_value, ...) return their value directly, and types that used to expose a fallible inherent clone() now implement Clone, which removes the hand-written deep-clone helpers that worked around that. Several of the removed branches looked like graceful handling (Worker env clone, Bun.spawn env block, ArrayBufferSink/HTTP stream writes, SQL buffer writes, markdown slug dedupe, package.json scripts map) but were unreachable: the callee never returned Err. The one real one, source map parsing reserving rows from an untrusted segment count, keeps its error path through MultiArrayList::try_ensure_total_capacity. Also removes the let _ = bindings on unit-returning calls that let_unit_value now flags, and the remaining bun.handleOom comments.
…e clone() StringArrayHashMap, StringSet and ArrayHashMap implement Clone now, so the entrywise MacroMap rebuilds in RuntimeTranspilerStore, jsc_hooks and JSTranspiler (clone_macro_map), and the hand-written Clone impls and clone() methods on ExternalModules, DependencyMap, Framework and ESMConditions collapse to .clone() / #[derive(Clone)]. Also drops the comments next to changed call sites that still described the removed Result plumbing.
17b6f92 to
aec6dc0
Compare
|
Rebased on main (conflicts were in computeChunks.rs, npm.rs, patch_install.rs, PackageManagerTask.rs, PackageInstall.rs and bun.lock.rs, all resolved in favor of main's newer code; main's new Also addressed the review on the remaining manual clones: the MacroMap rebuilds in RuntimeTranspilerStore.rs, jsc_hooks.rs and JSTranspiler.rs ( |
…iscarded results ESMConditions derives Clone now, so BundleOptions::deep_clone can clone it directly. The remaining StringSet::insert callers no longer bind the bool, and the comments that explained discarding a Result from put, insert and append_slice are gone along with the Results.
|
Pushed 55a42d9 for the two remaining review threads: the |
There was a problem hiding this comment.
I reviewed this PR across three passes; the earlier findings (redundant clone rebuilds, stale OOM comments) were addressed in aec6dc0 and 55a42d9, and this pass found no new issues. Given the scope — 247 files, a new nightly alloc_error_hook install in the crash handler, and API-surface changes to every allocating collection — a human sign-off on the overall design is still worthwhile.
What was reviewed: the bun_collections signature changes and their call-site rewrites (verified the compiler forces every dropped ?/handle_oom); the MultiArrayList try_ split and its one intentional caller in sourcemap parsing; rust_alloc_error_hook wiring and the new allocError() test hook; and that LinearFifo/yaml.rs were left alone as stated.
Extended reasoning...
Overview
This PR makes the allocation-only methods on bun_collections (ArrayHashMap, StringArrayHashMap, StringHashMap, HashMap, MultiArrayList, the bit sets, ByteVecExt, StringSet, PriorityQueue) return their value directly instead of Result<_, AllocError>, and removes the ~400 caller-side handle_oom/.unwrap_or_oom()/.expect("oom")/let _ = wrappers the compiler then flags. It installs std::alloc::set_alloc_error_hook in bun_crash_handler::install_hooks so any handle_alloc_error (from Vec, hashbrown, or the containers' own new abort paths) routes through the existing out_of_memory() crash report. Types whose inherent clone() was fallible now impl Clone (or #[derive(Clone)]), which let several manual deep-clone helpers and impl Clone blocks collapse. Two ratchets are added: clippy::let_unit_value is un-allowed, and the port-era source lint rejects bun.handleOom comments. A new crash_handler.allocError() test hook is added to the crash-handler test matrix.
Security risks
None identified. The change is error-plumbing removal around allocation paths that already could not return Err; the one place that genuinely reserved from untrusted input (sourcemap segment count) keeps its fallible path via the new try_ensure_total_capacity. No auth/crypto/permission logic is touched beyond mechanical wrapper removal.
Level of scrutiny
High. The individual edits are mechanical and compiler-enforced (a missed ? or wrong return type would not build, and the PR states cargo check / clippy / rust:check-all on 10 targets pass), but the aggregate is a design decision about OOM handling for the whole runtime: a nightly #[feature(alloc_error_hook)] is added, a process-global hook is installed, bun_dotenv::Error::Alloc is removed, and the intent is to eventually delete handle_oom entirely. That direction — and whether the sinks/SQL buffers listed as future try_ candidates are the right cut line — is worth a maintainer's eye.
Other factors
I left two prior rounds of findings on this PR (redundant MacroRemap/ESMConditions rebuilds and stale "result discarded"/"OOM-only" comments); all are marked resolved and the fixes are visible in the current diff and commit log. The PR adds a real regression test for the new hook (fails without it per the description), extends the source lint, and documents what was deliberately left for follow-up. The sheer breadth (247 files across bundler, install, runtime, jsc, crash handler) is the reason to defer rather than any specific concern.
|
Nothing further from my side: the review rounds are addressed and resolved, and CI on 55a42d9 is at 176 jobs passed with the last 3 still running, no failures so far (the lint workflows, including mordant, were already green on the previous push). Ready for a maintainer to look at the design, in particular the cut line described under Fix: bulk reservations sized from untrusted input keep a try_ path (source maps today), everything else aborts through the OOM report like Vec, and the sinks and SQL buffers get real try_ paths in a follow-up. |
|
CI is green on 55a42d9 (build 97543, all 179 jobs passed; the only test failures were per-test retries that passed on retry). Ready for review. |
…nk's output buffer cannot be allocated (#38941) ### Problem - A `TextEncoderStream` chunk whose encoded output cannot be allocated aborts the process: `memory allocation of N bytes failed`, then `panic(main thread): abort() called` and a "Bun has crashed" report. The output buffer is 1x to 3x the chunk, so for a large chunk this is where the API runs out of memory. - `encode_latin1_into` (`src/runtime/webcore/TextEncoderStreamEncoder.rs`) reserves infallibly at three places (the up-front reservation, the `reserve(2)` retry when a two-byte char does not fit, the grow-to-fit reservation) and returns nothing. `encode_utf16_into` returns a `Result`, but its main reservation is infallible too, and the replacement encoder it falls back to for invalid UTF-16, `to_utf8_list_with_type_bun` (`src/bun_core/string/immutable/unicode.rs`), returns `Result<_, AllocError>` while growing with `reserve_exact`, so its `Err` was unreachable. - The Zig version (`TextEncoderStreamEncoder.zig` at the parent of 23427db, lines 68 to 169) returned `throwOutOfMemoryValue()` at each of these sites; the port lost that. #33014 fixed the same regression in the decoding direction (`to_utf16_alloc*`, `TextDecoder`). ### Fix - Every reservation in `encode_latin1_into`, `encode_utf16_into` and `to_utf8_list_with_type_bun` is a `try_reserve` / `try_reserve_exact` mapped to `AllocError`. The amounts are unchanged: `ensure_total_capacity(len + remain + 1)` is `try_reserve(remain + 1)`, and the old `reserve_exact((i + count + len + extra) - len)` is `try_reserve_exact(i + count + extra)`. - `encode_latin1_into` returns `Result` like its UTF-16 sibling, and the two callers that did not handle an error yet (`encode_latin1`, and the Latin-1 arm of `TextEncoderStreamEncoder__encodeIntoSink`) throw out-of-memory the way `encode_utf16` and the UTF-16 arm already did. `to_utf8_list_with_type_bun` has no other caller. - Why this is the right behavior: the buffer's size is chosen by the caller's data, and a failure to get it was part of the JS contract before the port. The C++ transform step (`encodeAndEnqueue` in `JSTextEncoderStream.cpp`) already turns a pending exception into a rejected transform promise, so the `write()` rejects and the stream errors with `RangeError: Out of memory`; nothing else needed to change. This does not touch the abort-on-OOM policy for internal allocations; it is the same shape as #33014 and the `try_reserve` sites in `encoding.rs`, `PBKDF2.rs` and `TextDecoder`. - Verified with `test/js/web/encoding/text-encoder-stream.test.ts`, two ASAN-only tests (the gate and the ASAN lanes) that run a child under `allocator_may_return_null=1:max_allocation_size_mb=4`. JSC allocates the input strings outside that cap, so each of five inputs makes a different encoder reservation fail: the Latin-1 up-front reservation, the Latin-1 grow-to-fit reservation, the Latin-1 `reserve(2)` retry (an ASCII byte plus an odd number of two-byte chars leaves one spare byte), the UTF-16 simdutf-sized reservation, and the replacement encoder's first reservation (a lone surrogate ahead of N ASCII units: simdutf's N + 2 fits, 1.2N + 3 does not). One test feeds the chunks to a JS reader (write and read both reject with `RangeError: Out of memory`, and encoding still works afterwards); the other serves them through Bun.serve's native response sink, each behind a dangling lead surrogate so the output is assembled in the encoder's own buffer (the arm that stays the encoder's job if #36877 hands plain chunks to the sink directly), which also covers the prepend variant of every reservation; the errored transform cancels the source with the error and the server keeps serving. Without the fix both children abort on the first input (`memory allocation of 8388608 bytes failed`); the per-site sizes are in the details below. - Also run: `bun bd test test/js/web/encoding/` (577 pass), `test/js/web/streams/streams.test.js`, `test/regression/issue/29225.test.ts`, `test/js/node/test/parallel/test-whatwg-webstreams-encoding.js`, `cargo fmt --check`. - Scope: this PR originally also contained the `Bun.password` argon2 change; that is an unrelated allocation with its own design question and now lives in #39021. The same port regression exists in other user-sized buffers that are not touched here and have been handed off separately: the `Bun.gzipSync` / `gunzipSync` / `deflateSync` / `inflateSync` output buffers (`BunObject.rs`, including the one sized from the gzip trailer's ISIZE) and `Bun.zstdCompressSync` plus zstd decompression (`BunObject.rs`, `zstd/lib.rs`). The sinks and SQL buffers are covered by #38888's follow-up plan. ### Background - `Vec::try_reserve` returns `Err` when the allocator returns null, where `reserve` calls `handle_alloc_error` and aborts. Under ASAN the allocator is libc's, and `ASAN_OPTIONS=allocator_may_return_null=1:max_allocation_size_mb=N` makes it return null for any single allocation above N MiB, which is what makes these failures reproducible; JSC allocates JS strings through its own allocator, which the cap does not apply to. - `AllocError` is the unit error type the crates' `Error` enums wrap as `Alloc(..)`; `JSGlobalObject::throw_out_of_memory_value` sets JSC's out-of-memory `RangeError` as the pending exception. - A TextEncoderStream transform that throws rejects the transform promise, which errors both halves of the stream and, through `pipeThrough`, cancels the stream being piped in with the same error; that cancel reason is what the native-sink test observes. - Native-sink path: when the stream's readable is consumed by a native sink such as an HTTP response, `TextEncoderStreamEncoder__encodeIntoSink` encodes into a reusable buffer and hands it to the sink instead of enqueueing a `Uint8Array` per chunk. A lead surrogate left over from the previous chunk is encoded as a prefix of the next chunk's output, so that case always needs the encoder's buffer. <details> <summary>Per-site aborts without the fix (debug ASAN build), each size identifying the reservation that failed</summary> 48 MiB cap, JS path: ``` latin1, 64 MiB ASCII memory allocation of 67108864 bytes failed (up-front reservation) latin1, 40 MiB of 0xE9 memory allocation of 83886080 bytes failed (grow-to-fit, amortized from 40 MiB) latin1, "a" + odd run of 0xE9 memory allocation of 83886084 bytes failed (reserve(2) retry, amortized from 40 MiB + 2) utf16, 20M x U+65E5 memory allocation of 62914560 bytes failed (simdutf-sized reservation, 3 bytes/unit) utf16, U+D800 + 44 MiB of "a" memory allocation of 55364815 bytes failed (replacement encoder: 3 + 1.2 * 46137344) ``` 4 MiB cap (the sizes the tests use), with the fix, ASAN's refused allocation per case; JS path then native-sink path behind a carried lead surrogate (+3 bytes of replacement prefix in each reservation): ``` latin1 8388608 8388611 latin1Grow 6291456 6291462 latin1Stuck 6291460 6291466 utf16 6291456 6291459 utf16Invalid 4718595 4718598 ``` Every case reports `{"name":"RangeError","message":"Out of memory"}` and the child exits 0. </details> <!-- robobun:evidence:begin --> --- **[review]** gate passed · iteration 0 · 3 files touched <details><summary>fails on main (without fix)</summary> ```console ASAN without fix: BUILD FAILED (no junit output) $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/encoding/text-encoder-stream.test.ts ninja: Entering directory `/workspace/bun/build/debug' [1/85] gen BunProcess.lut.h Generating /workspace/bun/build/debug/codegen/BunProcess.lut.h from /workspace/bun/src/jsc/bindings/BunProcess.cpp [2/85] gen generated_host_exports.rs generated_host_exports.rs: 92 exports (host=3, lazy=10, generic=79, rust=0); 240 extern-C blocks audited [3/85] gen cpp.rs (cppbind) [4/85] gen JS modules (bundle-modules) Preprocess modules (11911ms) Bundle modules (45ms) Postprocesss modules (181ms) Bundle Functions (696ms) Generate Code (13ms) [12.87s] Bundled "src/js" for development 2826 kb 197 internal modules 13 native modules 91 internal functions across 17 files [4/80] cargo bun_bin → libbun_rust.a (--target x86_64-unknown-linux-gnu) nightly-2026-07-20-x86_64-unknown-linux-gnu unchanged - rustc 1.99.0-nightly (9f36de775 2026-07-19) [76/80] cxx obj/codegen/ZigGeneratedClasses.cpp.o FAILED: rust-target/x86_64-unknown-linux-gnu/debug/libbun_rust.a /workspace/bun/build/release/bun /work ... (truncated) release without fix: 2 skipped bun test v1.4.0-canary.1 (eabb96d) test/js/web/encoding/text-encoder-stream.test.ts: (pass) encoding one string of UTF-8 should give one complete chunk [0.61ms] (pass) a character split between chunks should be correctly encoded [0.06ms] (pass) a character following one split between chunks should be correctly encoded [0.04ms] (pass) two consecutive astral characters each split down the middle should be correctly reassembled [0.03ms] (pass) two consecutive astral characters each split down the middle with an invalid surrogate in the middle should be correctly encoded [0.06ms] (pass) a stream ending in a leading surrogate should emit a replacement character as a final chunk [0.02ms] (pass) an unmatched surrogate at the end of a chunk followed by an astral character in the next chunk should be replaced with the replacement character at the start of the next output chunk [0.03ms] (pass) an unmatched surrogate at the end of a chunk followed by an ascii character in the next chunk should be replaced with the replacement character at the start of the next output chunk [0.02ms] (pass) an unmatched surrogate at the end of a chunk followed by a plane 1 character split int ... (truncated) ``` </details> <details><summary>passes on PR (with fix)</summary> ```console ASAN with fix: all passed $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/web/encoding/text-encoder-stream.test.ts bun test v1.4.0 (3250eb4) test/js/web/encoding/text-encoder-stream.test.ts: (pass) encoding one string of UTF-8 should give one complete chunk [41.59ms] (pass) a character split between chunks should be correctly encoded [8.27ms] (pass) a character following one split between chunks should be correctly encoded [6.15ms] (pass) two consecutive astral characters each split down the middle should be correctly reassembled [6.95ms] (pass) two consecutive astral characters each split down the middle with an invalid surrogate in the middle should be correctly encoded [9.03ms] (pass) a stream ending in a leading surrogate should emit a replacement character as a final chunk [4.68ms] (pass) an unmatched surrogate at the end of a chunk followed by an astral character in the next chunk should be replaced with the replacement character at the start of the next output chunk [5.68ms] (pass) an unmatched surrogate at the end of a chunk followed by an ascii character in the next chunk should be replac ... (truncated) release with fix: 2 skipped $ bun scripts/build.ts --profile=release [configured] bun-profile → bun (stripped) target linux-x64-gnu build type Release build dir ./build/release revision 3250eb4 features baseline 22 deps, 123 codegen, 1176 objects in 1165ms ninja: Entering directory `/workspace/bun/build/release' [1/1238] gen ErrorCode+*.h [2/1238] gen bindgenv2 [3/1238] install /workspace/bun bun install v1.4.0-canary.1 (eabb96d) Checked 107 installs across 153 packages (no changes) [74.00ms] [4/1238] install /workspace/bun/packages/bun-error bun install v1.4.0-canary.1 (eabb96d) Checked 1 install across 2 packages (no changes) [4.00ms] [5/1238] fetch zlib [zlib] up to date [6/1238] gen .bind.ts → GeneratedBindings.cpp [7/1238] fetch tinycc [tinycc] up to date [8/1237] fetch libjpeg-turbo [libjpeg-turbo] up to date [9/1237] install /workspace/bun/src/node-fallbacks bun install v1.4.0-canary.1 (eabb96d) Checked 129 installs across 147 packages (no changes) [55.00ms] [10/1237] subst deps/zlib/zconf.h [11/1237] subst deps/zlib/zlib.h [12/1188] gen JSEvent.lut.h Generating /workspace/bun/build/release/codegen/JSEvent.lut.h from /workspace/bun/src/jsc/ ... (truncated) ``` </details> <details><summary>diff hotspot</summary> ``` src/bun_core/string/immutable/unicode.rs | 17 +-- src/runtime/webcore/TextEncoderStreamEncoder.rs | 49 ++++--- test/js/web/encoding/text-encoder-stream.test.ts | 163 ++++++++++++++++++++++- 3 files changed, 195 insertions(+), 34 deletions(-) ``` </details> **gate history** · 1 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests src/bun_core/string/immutable/unicode.rs 5 5 0 src/runtime/webcore/TextEncoderStreamEncoder.rs 4 11 0 test/js/web/encoding/text-encoder-stream.test.ts 3 7 0 ``` </details> <!-- robobun:evidence:end -->
Problem
Result<_, AllocError>plumbing in the tree is a leftover of the Zig port: the callee grows aVecor a hashbrown table, which aborts on allocation failure, and then returnsOkunconditionally.ArrayHashMap::put,get_or_put,ensure_unused_capacity,StringHashMap::put,HashMap::put,ByteVecExt::write, the bit sets and so on all worked this way (array_hash_map.rseven said "kept asResultfor API stability").bun_core::handle_oom(..),.unwrap_or_oom(),.expect("oom"),?,.map_err(|_| OutOfMemory)?,let _ = ..; // OOM/capacity: fire-and-forget,// bun.handleOomcomments, and branches that look like graceful OOM handling but are unreachable (Worker env clone returning "Out of memory",Bun.spawnthrowing OOM for the env block, ArrayBufferSink / HTTP stream writes returning ENOMEM, SQL buffer writes mapping toOutOfMemory, markdown slug dedupe andpackage.jsonscriptssilently giving up).let _ = self.graph.ast.append(..)on aMultiArrayList(the one container whoseResultwas real) would have silently desyncedgraph.astfromgraph.input_fileshad the allocation ever failed.handle_oomproduced an out-of-memory crash report,.expect("oom")a panic report saying it is a bug in Bun, and a plainVecgrowth failure went through std'shandle_alloc_error, printingmemory allocation of N bytes failedand aborting, which is reported as anabort()crash (or, on ASAN builds, not at all). Nothing hooked std's alloc error path.Fix
bun_collections: the allocation-only methods return their value directly.MultiArrayListand the dynamic bit sets, which allocate by hand, callstd::alloc::handle_alloc_erroron failure exactly likeVecdoes. Types whose inherentclone()returnedResultimplementCloneinstead (ArrayHashMap,StringArrayHashMap,StringSet, the bit sets,bun_dotenv::Map,DeclaredSymbolList), which also removes the hand-written deep-clone code that existed to work around that (clone_macro_remap,clone_macro_map,Map::clone_with_allocator, theMacroRemaprebuilds intranspiler.rs,RuntimeTranspilerStore.rsandjsc_hooks.rs, and the manualCloneimpls /clone()methods onExternalModules,DependencyMap,FrameworkandESMConditions, which are derives now).bun_crash_handler::install_hooksregistersstd::alloc::set_alloc_error_hooknext to the panic hook, routing into the existingout_of_memory()path. With that, dropping the wrappers is purely a cleanup: a failed allocation anywhere in Rust code now produces the same "Bun has run out of memory" reporthandle_oomproduced, and this is also what makes thehandle_alloc_errorcalls in the containers correct. The hook does not allocate (one diverging call); a second failure inside the report hits the existing re-entry guard.bun_dotenv::MapandLoader::load_process/load_node_js_config,DeclaredSymbolList,semver::StringPool,PathToSourceIndexMap,SavedSourceMap::put_value/put_mappings,LineOffsetTable::generate,mime_type::create_hash_table,ensure_stale_bit_capacity,register_barrel_with_deferrals, ...).bun_dotenv::Error::Allocand thebun_allocdependency ofbun_dotenv/bun_http_typesare gone because nothing produces anAllocErrorthere anymore.Resultis kept: source map parsing reserves rows from the segment count of an untrusted map and turns a failed reservation into a parse error for the user. It now goes throughMultiArrayList::try_ensure_total_capacity(theVec::try_reserveanalog); everything else that reached thatResultcrashed onErranyway.LinearFifo(a fullStaticBufferis reported through the same error andChannelblocks on it; it needs its own error type),yaml.rs(usesAllocErroras its merge-key budget error), and the next layer of functions that still returnResult<_, AllocError>(about 470, roughly half of which can no longer fail after this change), which cascade further and are for follow-up PRs, ending with deletinghandle_oom/UnwrapOrOomand giving the few user-facing buffers that should survive OOM (the sinks and SQL buffers above) realtry_paths.clippy::let_unit_valueis no longer allowed (the 174 remaininglet _ = unit_call();bindings are removed;cppbind.tsstops emittinglet __r =for void C++ functions so the generated wrappers pass too), and the port-era source lint now rejectsbun.handleOomcomments (all 29 removed).test/cli/run/run-crash-handler.test.ts: newcrash_handler.allocError()hook callshandle_alloc_error; added to the terminal-signal and automatic-reporter matrices. Fails without the hook (memory allocation of 4096 bytes failed, no report on the ASAN debug build), passes with it.test/internal/source-lints/port-era-markers.test.tswith the new pattern.cargo check --workspace --tests,cargo clippy --workspace(clean, withlet_unit_valueenforced),bun run rust:check-all(10 of 10 targets),cargo testandcargo miri testforbun_collections.bun bd teston sourcemap, env, bundler (barrel, splitting, html manifest), transpiler, isolated install / patch / audit, sql, glob, fs.watch, http response streams, http2, framework router suites.Background
AllocErroris the unit error type every crate'sErrorenum wraps asAlloc(..); in Zig every allocating call returnederror.OutOfMemoryandbun.handleOom(x)was the idiom for "crash with an OOM report if this failed". The port translated the signatures literally, but Rust'sVec,Boxand hashbrown never return an error: on failure they callhandle_alloc_error, so aResultreturned from code built on them can only ever beOk.handle_alloc_errorconsults a process-global hook (std::alloc::set_alloc_error_hook, a nightly API; the workspace is already pinned to nightly) and aborts if the hook returns. Bun'sout_of_memory()never returns: it prints the crash report withCrashReason::OutOfMemoryand exits, which is what distinguishes an OOM from a bug in the crash telemetry.MultiArrayListis a struct-of-arrays container that allocates its columns with a rawAllocator, and the dynamic bit sets realloc a word buffer by hand, so those two were the only collections whoseResultcould actually beErr; they now have the same split asVec(ensure_*aborts,try_ensure_*reports).clippy::let_unit_valueflagslet _ = f();wherefreturns(); it had been allowed inCargo.tomlbecause the port produced these bindings wherever a formerly fallible call became infallible.This also covers the hook proposed in #31690, which this PR supersedes.
Counts
Occurrences in
src/**/*.rs, main vs this branch:handle_oom(.unwrap_or_oom().expect("oom")/.expect("OOM")// OOM/capacitycomments// OOM-onlycommentsbun.handleOomcommentslet _ = <unit>(clippy)The remaining ones wrap functions outside
bun_collectionsthat still returnResult<_, AllocError>(e.g.semver::Builder::allocate,AstBuilder,ini'sOOM<T>,enqueue_*inbundle_v2); those are the next layer.no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/run/run-crash-handler.test.ts