Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7797,6 +7797,9 @@ declare module "bun" {
* This can improve performance when you don't need to read output
* immediately.
*
* Has no effect when `maxBuffer` is set. Output is counted against
* `maxBuffer` as it is read, so reading starts right away.
*
* @default false
*
* @example
Expand Down
11 changes: 7 additions & 4 deletions src/runtime/api/bun/js_bun_spawn_bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1696,15 +1696,18 @@ fn spawn_maybe_sync(
}
}

// `maxBuffer` is charged as bytes are read, so a paused reader would never enforce it.
let lazy = !is_sync && lazy && max_buffer.is_none();

// Start the readers before the Writable::Buffer stdin writer so that if
// the writer's start() throws below, both PipeReaders have taken their
// start() ref and on_process_exit's later drain is refcount-balanced.
if let Readable::Pipe(pipe) = subprocess.stdout.get() {
// Note: pass `subprocess_nn` (the `NonNull<Subprocess<'static>>`
// captured above) instead of the live `&mut subprocess`, which would
// alias with the `&mut subprocess.stdout` borrow held by `pipe`.
Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, !is_sync && lazy);
if (is_sync || !lazy) && matches!(subprocess.stdout.get(), Readable::Pipe(_)) {
Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, lazy);
if !lazy && matches!(subprocess.stdout.get(), Readable::Pipe(_)) {
if let Readable::Pipe(pipe) = subprocess.stdout.get() {
Readable::pipe_reader_mut(pipe).read_all();
}
Expand All @@ -1713,9 +1716,9 @@ fn spawn_maybe_sync(

if let Readable::Pipe(pipe) = subprocess.stderr.get() {
// Note: see stdout arm above — avoid aliased &mut.
Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, !is_sync && lazy);
Readable::pipe_reader_mut(pipe).start(subprocess_nn, event_loop_nn, lazy);

if (is_sync || !lazy) && matches!(subprocess.stderr.get(), Readable::Pipe(_)) {
if !lazy && matches!(subprocess.stderr.get(), Readable::Pipe(_)) {
if let Readable::Pipe(pipe) = subprocess.stderr.get() {
Readable::pipe_reader_mut(pipe).read_all();
}
Expand Down
31 changes: 31 additions & 0 deletions test/js/bun/spawn/spawn-maxbuf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,37 @@ describe.each(["stdout", "stderr"] as const)("maxBuffer kills the process after
});
});

// `lazy: true` defers the pipe reads until JS first pulls, and `maxBuffer` is
// only charged as bytes are read. `maxBuffer` must still count the output of a
// child whose pipes nothing has read yet.
describe.each(["stdout", "stderr"] as const)("maxBuffer kills the process with lazy: true and .%s unread", fd => {
// The child writes well past `maxBuffer` and then blocks forever. Without the
// kill, `proc.exited` never resolves and the test times out.
const firehose = `process.${fd}.write(Buffer.alloc(300000, 65).toString()); setInterval(() => {}, 1e9);`;
const killSignal = isWindows ? "SIGKILL" : "SIGHUP";

test.concurrent("Bun.spawn", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "-e", firehose],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
lazy: true,
maxBuffer: 1000,
killSignal,
});
await proc.exited;
// A late reader still gets what was read up to the limit.
const bytes = await proc[fd].bytes();
expect(bytes.length).toBeGreaterThan(1000);
expect(bytes.length).toBeLessThanOrEqual(1000 + 64 * 1024);
expect({ exitCode: proc.exitCode, signalCode: proc.signalCode }).toEqual({
exitCode: null,
signalCode: killSignal,
});
});
});

describe("maxBuffer infinity does not limit the number of bytes", () => {
const sample = "this is a long example string\n";
const sample_repeat_count = 10000;
Expand Down
Loading