Skip to content

Blob: read file-backed parts in multi-part new Blob([...]) - #33600

Open
robobun wants to merge 6 commits into
mainfrom
farm/a5b5509f/blob-file-part-bytes
Open

robobun wants to merge 6 commits into
mainfrom
farm/a5b5509f/blob-file-part-bytes

Conversation

@robobun

@robobun robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #25851

Problem

  • A file-backed Blob (Bun.file(p) or its slice) gives zero bytes as one part of new Blob([...]). No error occurs, and .size agrees with the wrong data.
  • from_js_without_defer_gc (src/runtime/webcore/Blob.rs) pushes blob.shared_view(), which is empty for a store that is not in-memory bytes.

Fix

  • An in-memory part keeps the inline code of main. A file part goes to one cold function, push_file_part: open with O_NONBLOCK, fstat, then pread.
  • A missing, unreadable or directory part throws ENOENT, EACCES or EISDIR. A pipe, device or S3 part throws a TypeError and is not opened.
  • Verified: test/js/web/fetch/blob.test.ts on Linux and Windows x64 (main fails the 9 new tests), and 5 related suites.
  • Self-reviewed: 10 concerns raised, 7 addressed. Notes has the other 3.

Background

Downsides

  • The constructor reads a file part on the JS thread: about 150 ms for 100 MB, 1.4 s for 1 GiB. Peak memory is 2 times the file.
  • A multi-part Blob is a snapshot. A part that main dropped in silence now throws, synchronously also from Bun.write(dest, [...]).
  • Each constructor call and each in-memory Blob part costs 1 instruction more. The binary grows by 8,192 bytes.
Notes

What a user sees

const file = Bun.file(p); // 10 bytes: "ABCDEFGHIJ"
await new Blob([file]).text();          // "ABCDEFGHIJ" (one part: shares the lazy store)
await new Blob([file, "-tail"]).text(); // main: "-tail"   this PR: "ABCDEFGHIJ-tail"
await new Blob(["head-", file]).text(); // main: "head-"   this PR: "head-ABCDEFGHIJ"
await new Blob(["", file]).text();      // main: ""        this PR: "ABCDEFGHIJ"

Each way to get a file store is affected: Bun.file(path), Bun.file(fd), structuredClone(Bun.file(p)), a BunFile from postMessage, await new Response(Bun.file(p)).blob(), and slices of these.

Errors

part result
missing file ENOENT, syscall open, with the path
unreadable file EACCES, syscall open, with the path
directory EISDIR, syscall read, with the path
FIFO, socket, device, Bun.stdin as a pipe TypeError: Blob parts backed by a pipe, socket or device cannot be read synchronously; await .bytes() or .arrayBuffer() first
S3 TypeError: Blob parts backed by S3 cannot be read synchronously; await .bytes() or .arrayBuffer() first

A path is checked with stat before it is opened. The open of a FIFO wakes a writer that waits for a reader, and the writer then fails or loses its bytes. The test for the FIFO runs a writer, lets the constructor throw, and then reads the bytes of the writer with cat.

The errors are thrown by the call that joins the parts. For new Blob, new File and new Response([...]) that is the constructor. Bun.write(dest, [...]) joins the array before it makes a promise, so it throws synchronously and does not reject:

call main this PR
Bun.write(out, Bun.file(missing)) rejects ENOENT rejects ENOENT
Bun.write(out, ["h", Bun.file(existing)]) resolves 1, writes h resolves 4, writes hSRC
Bun.write(out, ["h", Bun.file(missing)]) resolves 1, writes h throws ENOENT synchronously, out is not created
Bun.write(out, [{ toString() { throw } }]) throws synchronously throws synchronously

Instruction counts (release builds of main 8d36bff51 and of this branch, Linux x64, JIT off)

perf_event_open is not permitted in the build container and valgrind is not installed. The counts come from a ptrace single-step counter. It counts the instructions of the main thread between two marker syscalls. Repeated runs differ by less than 60 instructions in 6 million. Each value is the slope between 1,000 and 2,000 calls.

call main this PR delta
new Blob(["a", "b"]) 3,135.7 3,136.7 +1
new Blob([u8, "a"]) 3,208.5 3,209.4 +1
new Blob([blob, "a"]) 3,230.5 3,232.4 +2
new Blob([file, "a"]) (in-memory File) 3,230.5 3,232.4 +2
new Blob([slice, "a"]) 3,232.8 3,234.8 +2
new Blob([16 in-memory blobs]) 8,800.2 8,817.2 +17

The earlier head of this PR cost 30 more for each in-memory part.

Binary size: bun 80,840,224 to 80,848,416 bytes (+8,192). bun-profile 169,397,392 to 169,408,152 bytes (+10,760).

Synchronous read of a file part (release, warm page cache, one constructor call for each process, 15 runs each)

file time peak RSS
100 MB 129 to 167 ms 232 MB
1 GiB 1.25 to 1.86 s 2,080 MB

An empty script has a peak RSS of 28 MB. The peak is 2 times the file: the read buffer, and the joined buffer of the new Blob.

A part against the same part read alone

A grid of 912 cells compares await part.bytes() with await new Blob(["", part]).bytes(). It has 8 ways to make the part, 19 slice() argument lists, a nested slice, and a file that grows or shrinks after the part is made.

  • main: 650 cells differ. The part side is empty in each of them.
  • This PR: 129 cells differ. Each of them also differs on main.
    • 108 are negative-index slices of a file whose size was never read, for example Bun.file(p).slice(-3). The slice itself is wrong on main ("ABC" for the file above). Bun.file().slice(): count a negative index back from the file's real end #41257 corrects that at slice time.
    • 21 are an fd whose cursor was moved before the read. The lazy reader reads from the cursor. The part reads at the offset of the Blob, so two parts over one fd give the same bytes.

Review: not changed

  • A file near the size of the RAM. The first reserve is capped at 8 GiB and each reserve can fail with ENOMEM, but Linux overcommit can let the OOM killer act first. fs.readFile has a RAM guard for this. This PR does not add one: if that guard regresses, its test reads a file as large as the RAM.
  • A stat size that is stale. The part stops at the size that fstat gave after the open. A file that grows during the read gives a valid snapshot in both readers.
  • bun_sys::File does not close fds 0 to 2 on drop. If user code closed a stdio fd, the open for a part can get that fd and keep it. The limit is 3 fds, and the rule belongs to bun_sys::File.
  • The FormData serializer still reads a file entry with NodeFS::read_file: a FIFO entry blocks, an fd entry reads from the cursor, an S3 entry gives no bytes. It reads Bun.stdin as a pipe today, so a move to push_file_part changes behaviour and needs its own PR.

Platform notes

  • On POSIX, pread does not move the cursor of the fd of the caller. On Windows, a positioned ReadFile on a synchronous handle moves it, so the test asserts the cursor only on POSIX.
  • Bun.file() of a file that is embedded in a compiled executable has an in-memory store, so it never reaches push_file_part.
  • The null device as a part (Bun.file("/dev/null"), NUL on Windows) throws the TypeError at this head. main gave an empty part for it, which is the right result. Blob: a null device part is an empty part #43944 is stacked on this PR and makes the null device an empty part again.

Tests

  • bun bd test test/js/web/fetch/blob.test.ts (debug, ASAN): 119 pass, 1 skip. The skip is the EACCES test, which does not run as root. As uid 65534 the block gives 10 pass.
  • A Windows x64 debug build of main plus this diff: 115 pass, 4 skip (before the FIFO test was split in two).
  • A release build of main fails the 9 tests that run in the new block.
  • macOS: the first version of the FIFO test read the FIFO in the child with await Bun.file(fifo).text(), and the child never exited there (Buildkite build 120425, both macOS lanes). The constructor is not involved, so the test now reads with cat. The lazy reader on macOS is a separate problem.
  • Also ran: blob-array-fast-path.test.ts, blob-cow.test.ts, structured-clone-blob-file.test.ts, bun-file.test.ts, body-clone.test.ts (149 pass).

Earlier shape of this PR

The first version sent each Blob part through one helper, push_blob_part_bytes. It read a path with NodeFS::read_file (a blocking open, and one more copy of the file) and an fd with the pread loop. Review rounds on that version added: pread for an fd so that a repeated part does not read from the end, the st_size == 0 case for virtual files such as procfs, a fallible allocation with a first reserve of 8 GiB at most, and a clamp of a slice to the size of the file.


no test proof · iteration 11 · platform-specific test(s) that do not run on this machine, deferring to CI, which covers all platforms: test/js/web/fetch/blob.test.ts

@coderabbitai

coderabbitai Bot commented Jul 7, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: oven-sh/bun/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Essentials

Run ID: 9c36bf10-d2de-4c72-afa6-f2e2e3d23963

📥 Commits

Reviewing files that changed from the base of the PR and between 0832a6d and 14a5bbd.

📒 Files selected for processing (1)
  • test/js/web/fetch/blob.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.


Walkthrough

Blob construction now reads file-backed parts when their in-memory views are empty. The change handles path-backed and fd-backed files, and rejects unsupported synchronous reads. Tests cover mixed parts, file changes, slicing, and error cases.

Changes

Blob part byte contribution

Layer / File(s) Summary
Blob part byte resolution
src/runtime/webcore/Blob.rs
The array walk and deferred part handling now read file-backed parts with empty views. Reads use the Blob offset and size window. The helper rejects S3-backed and non-regular files and reports filesystem and allocation errors as JavaScript exceptions.
File-backed Blob construction tests
test/js/web/fetch/blob.test.ts
Tests cover mixed-part byte order, file slices after file-size changes, fd-backed reads, caller fd position, unreadable files, non-regular files, and zero-size procfs reads.

Suggested reviewers: jarred-sumner

Priority: ➖ Normal

Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 14a5b

The reported mixed-part behavior is covered by the changed implementation and tests. No identified issue currently prevents merging after normal checks.

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR satisfies [#25851]. BlobExt::from_js_without_defer_gc now reads file-backed parts during multipart Blob construction. The read uses the Blob offset and size, so file bytes contribute in eit…
Out of Scope Changes check ✅ Passed The changes stay within [#25851]. The Rust changes implement synchronous reads for file-backed Blob parts. The tests cover related file stores, slices, file-size changes, and expected errors for unsup…
Title check ✅ Passed The title clearly and concisely describes the main change: reading file-backed parts during multi-part Blob construction.
Description check ✅ Passed The description explains the problem, implementation, behavior changes, tradeoffs, errors, and verification results. It does not use the exact template headings, but it provides the required informati…

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the claude label Jul 7, 2026
@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 3:57 PM PT - Sep 24th, 2026

✅ @robobun, your commit 14a5bbd91e55da15f7bbfef3bacd1ad403f8db11 passed in Build #120442! 🎉


🧪   To try this PR locally:

bunx bun-pr 33600

That installs a local version of the PR into your bun-33600 executable, so you can run:

bun-33600 --bun

@github-actions

github-actions Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Found 1 issue this PR may fix:

  1. Blob constructor ignores File parts if more than one part is given #25851 - Blob constructor ignores File parts when more than one part is given, producing zero bytes — exactly the shared_view() bug this PR fixes in from_js_without_defer_gc

If this is helpful, copy the block below into the PR description to auto-close this issue on merge.

Fixes #25851

🤖 Generated with Claude Code

Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs
@robobun

robobun commented Jul 7, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: ready for review at 14a5bbd, which includes a merge with main. CI is green: build 120442 passed all 181 jobs.

Reproduce on main: await new Blob([Bun.file(p), "-tail"]).text() gives "-tail". The file part is dropped.

Proof: bun bd test test/js/web/fetch/blob.test.ts -t "file-backed Blob part" fails 9 tests on main and passes on this branch. I ran it on Linux x64 (debug with ASAN, and as a non-root user for the EACCES test) and on a Windows x64 build.

Open question for a maintainer: the constructor reads a file part synchronously and throws when it cannot. The PR body gives the cost and the cases under Downsides.

@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from c97ea66 to 4ff7093 Compare July 9, 2026 14:14
Comment thread test/js/web/fetch/blob.test.ts Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from 4a5b278 to bfd3bc2 Compare July 25, 2026 13:25
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated
@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from bfd3bc2 to ee3facf Compare July 25, 2026 14:03
Comment thread src/runtime/webcore/Blob.rs Outdated
Comment thread src/runtime/webcore/Blob.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new issues found — all five concerns from my earlier rounds (fd cursor via pread, the size==0 ENOENT guard, the S3 error wording, Windows pread pointer semantics, and the st_size==0 procfs case) are addressed in the current diff. Deferring to a maintainer because this introduces synchronous disk I/O into the new Blob([...]) constructor path and changes observable behavior (ENOENT now thrown where file parts were previously silently dropped), which is a design call worth a human sign-off. Note comment-cop has left several unresolved flags on the new code comments; the ones I see in the current diff are 2-3 line invariant notes rather than workaround justifications, so they may just need dismissing.

What was reviewed: the borrow-vs-clone split in push_blob_part_bytes and its detach_lifetime safety contract against the caller's prescan; the Path arm's read_file ownership/free of the returned buffer; the Fd arm's pread grow-loop for cap sizing, offset arithmetic, and short-read handling; and the new tests for hermeticity and platform gating.

Extended reasoning...

Overview

The PR fixes #25851: file-backed Blob parts (Bun.file(path), Bun.file(fd), slices/clones thereof) contributed zero bytes when used as one of multiple parts in new Blob([...]) / new File([...]). The fix extracts a push_blob_part_bytes helper that dispatches on the store variant: in-memory bytes borrow or clone as before, path-backed files go through NodeFS::read_file, fd-backed files use a pread grow-until-EOF loop at the Blob's absolute offset, and S3-backed parts throw a clear error. ~120 lines of new Rust in src/runtime/webcore/Blob.rs plus ~70 lines of tests in blob.test.ts.

Security risks

None identified. Inputs are the caller's own file paths/fds; there is no untrusted-length parsing. Buffer sizing uses saturating_sub/saturating_mul/saturating_add and clamps against fstat size or the slice's concrete size. The one unsafe block (detach_lifetime on the borrowed bytes view) is gated on the same prescan invariant the pre-PR code relied on and is documented at both the call site and the helper.

Level of scrutiny

Medium-high. This is core Web API surface (Blob/File constructors) and native code with an unsafe block, cross-platform I/O, and a user-visible behavior change: constructing a Blob with a nonexistent file part now throws ENOENT synchronously instead of silently contributing nothing. It also introduces synchronous disk reads into a constructor that was previously pure over its arguments — that is the correct-per-spec behavior (the constructor is sync and "process blob parts" cannot be deferred), but it is an architectural choice a maintainer should confirm rather than something I should approve unilaterally.

Other factors

This PR has been through two rounds of my review; all five prior findings were addressed with follow-up commits and tests (fd-twice, ENOENT-after-.size, POSIX-only cursor check, procfs grow loop). The bug-hunting pass this run found nothing new. Test coverage is thorough across path/fd/slice/clone/Response.blob() variants and the error path. The outstanding comment-cop bot flags target 2-3 line invariant comments (the push_blob_part_bytes doc comment, the pread-vs-read_file rationale) that read as durable non-obvious content to me, but they are unresolved and the author should either trim or dismiss them.

@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from ee3facf to 5e59f0f Compare July 25, 2026 15:20
Comment thread src/runtime/webcore/Blob.rs Outdated
@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from 5e59f0f to 2acd433 Compare July 25, 2026 17:08
Comment thread src/runtime/webcore/Blob.rs Outdated
@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from 2acd433 to dc6cee1 Compare July 25, 2026 17:50
Comment thread src/runtime/webcore/Blob.rs Outdated
The multi-part join in from_js_without_defer_gc pushed blob.shared_view()
for every Blob part, but shared_view() returns an empty slice for any
non-Bytes store (Data::File, Data::S3). Every file-store Blob used as one
part of a multi-part new Blob([...]) or new File([...]) therefore
contributed zero bytes. The single-part fast path (new Blob([Bun.file(p)]))
was unaffected because it shares the store instead of joining bytes.

Fix: dispatch on the part's store. Data::Bytes keeps the shared_view()
path. Data::File reads the file synchronously (the constructor is sync and
the spec's "process blob parts" requires the bytes at construction time):
path stores use NodeFS::read_file; fd stores use pread so repeated use of
the same fd part reads the correct window regardless of cursor position.
Data::S3 throws rather than silently contributing zero bytes, since there
is no synchronous S3 read path.

A nonexistent file part now throws ENOENT from the constructor instead of
being silently ignored.

Fixes #25851
@robobun
robobun force-pushed the farm/a5b5509f/blob-file-part-bytes branch from dc6cee1 to 2f30b03 Compare July 25, 2026 18:57

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The over-allocation concerns from the last round are addressed in 2f30b03 — both the empty-fd-huge-slice and sparse-file triggers now go through try_reserve* with the 8 GiB cap and surface ENOMEM instead of aborting, and the grow loop is likewise fallible. I didn't find anything further.

Deferring to a human for the design/behaviour sign-off rather than the mechanics: new Blob([...]) now does synchronous disk I/O and throws (ENOENT for a missing path, an error for S3 parts) where it previously returned successfully — that's the right fix for #25851 but is a user-visible change a maintainer should confirm.

What was reviewed:

  • Fd arm buffer sizing: traced empty-file+huge-slice (starts at 8K, first pread returns 0) and sparse st_size (capped at 8 GiB via try_reserve_exact, grow loop via try_reserve) — no infallible allocation remains.
  • 8 << 30 literal is inferred as usize via .min(), so no i32 overflow on 64-bit targets.
  • Path arm: push_cloned before buf.destroy(), so no UAF; encoding fixed to Buffer.
  • Both call sites (array iterator and deferred-stack arm) route through the helper; the deferred arm passes borrow_bytes=false matching the prior always-copy behaviour.
Extended reasoning...

Overview

Adds push_blob_part_bytes (~110 lines) to src/runtime/webcore/Blob.rs so file-backed Blob parts in a multi-part new Blob([...]) contribute their bytes instead of being silently dropped. The Path arm delegates to NodeFS::read_file; the Fd arm hand-rolls a pread loop with fstat-derived sizing, an 8 GiB initial cap, fallible try_reserve*, and grow-until-EOF for st_size==0 virtual files. S3-backed parts throw. Adds ~70 lines of tests in blob.test.ts covering path/fd, slices, repeated fds, past-EOF slices, empty-fd huge slices, structuredClone, Response.blob(), and ENOENT.

Verification of last round's fix

I traced 2f30b03 against both triggers from my previous review:

  • Empty file + .slice(0, 1e12): file_len==0 → initial = 8192.min(cap) = 8192, first pread returns 0, loop breaks. Test added.
  • Sparse file (st_size=1e12): initial = cap = 1e12, but try_reserve_exact(initial.min(8<<30)) caps at 8 GiB and throws ENOMEM on failure; the grow loop's try_reserve is likewise fallible. No vec[...] or infallible resize-past-capacity remains.

The resize calls follow a successful try_reserve* for the same delta, so they cannot reallocate. new_len - buf.len() cannot underflow because new_len = buf.len().saturating_mul(2).min(cap) and the branch is only entered when buf.len() < cap.

Security risks

The user-controlled-size → infallible-allocation abort was the security-relevant issue; it is now closed. The Path arm inherits read_file's existing safeguards. No path traversal or injection surface — inputs are already-constructed Bun.file handles.

Level of scrutiny

High. This is a Web-standard constructor (new Blob/new File) that now performs synchronous disk I/O and can throw where it previously could not. Four prior review rounds each found a real bug (Windows pread cursor semantics, procfs st_size==0, realloc churn, two over-allocation aborts), which is evidence the code is subtle enough to warrant a maintainer's eyes even though this pass found nothing.

Other factors

All prior inline threads are resolved and the comment-cop paragraph-comment flags were addressed (comments in the current diff are terse). Test coverage is broad and each earlier finding has a corresponding regression assertion. The remaining question is design intent — whether synchronous read-on-construct (and the new throw behaviour) is the approach the maintainers want, versus e.g. keeping the store lazy and materializing on first read. That's a maintainer call, not a correctness bug, so I'm deferring rather than approving.

…blocking open

The in-memory arm is back at the two call sites as main has it. A part
whose shared_view() is empty and whose store is a file or S3 goes to one
cold function, so an in-memory part costs one emptiness check more than
main.

The path arm no longer calls NodeFS::read_file. It opens with O_NONBLOCK,
fstats, and shares the fd arm's pread loop, so a FIFO with no writer can
not block the JS thread, and the file is copied once less. A directory
still throws EISDIR. A pipe, socket or device now throws a TypeError that
names the async readers.
…s and the FIFO writer

A path is now stat'ed first. A pipe, socket or device is refused without
open(2), because the open of a FIFO wakes its writer, and the writer then
fails before the async read that the error message names can run.

Tests: every byte value and UTF-8 text, a part against the same part read
alone (slices, a file that grows or shrinks), the path in ENOENT and
EISDIR, an S3 part, a procfs file whose stat size is 0, and a FIFO whose
writer still delivers to the async read after the constructor threw.
@robobun

robobun commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator Author

Pushed a rework on top of a merge with main (0832a6d, then 14a5bbd for the FIFO test on macOS).

  • In-memory parts keep the inline code of main. Only a file or S3 part calls the new cold function push_file_part. An in-memory Blob part now costs 1 instruction more than on main. The earlier head cost 30 more.
  • A path part is checked with stat, opened with O_NONBLOCK, and read with the same pread loop as an fd part. A FIFO with no writer cannot block the constructor. A pipe, socket or device throws a TypeError and is not opened, so a writer that waits on the FIFO still delivers to the next reader.
  • One copy less: the path no longer goes through the NodeFS::read_file buffer.
  • Tests added: every byte value, a part against the same part read alone (slices, a file that grows or shrinks), the path in ENOENT and EISDIR, an S3 part, a procfs file whose stat size is 0, and the FIFO with a writer.

The PR body has the measured costs (read time, peak memory, instruction counts, binary size) and the 3 review concerns that I did not change, each with the reason.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline findings, I also checked the fd lifetime in push_file_part (the opened bun_sys::File is closed on Drop on every return path, and caller-owned fds are read with pread only, never closed) and the allocation bounds from the earlier round (slice size is now clamped to st_size, the initial reservation is capped at 8 GiB and goes through try_reserve, so the over-allocation abort is closed) — both look correct.

Extended reasoning...

The change adds a synchronous file-read helper to the multi-part Blob/File constructor in src/runtime/webcore/Blob.rs and a test block in test/js/web/fetch/blob.test.ts; it touches filesystem access from user-supplied paths and fds but no auth or credential surface. Three verified findings are posted inline (synchronous throw instead of rejection for Bun.write, event-loop stall on large files, and the FormData serializer not receiving the same fix), so approval is not appropriate; this note only records what else was examined and ruled out.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread src/runtime/webcore/Blob.rs
Comment thread src/runtime/webcore/Blob.rs
Comment thread src/runtime/webcore/Blob.rs
On macOS the child never exited after `await Bun.file(fifo).text()`, so
the test timed out. The test is about the constructor: it must refuse the
FIFO without an open, so that the writer keeps its bytes for the next
reader. `cat` is that reader now. The pipe case (`Bun.stdin`) is a test
of its own.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings marked 🟡 are optional suggestions and need no follow-up push.

Comment thread test/js/web/fetch/blob.test.ts
Comment thread test/js/web/fetch/blob.test.ts

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Blob constructor ignores File parts if more than one part is given

2 participants