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
9 changes: 7 additions & 2 deletions src/js/node/child_process.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const ArrayPrototypeSplice = Array.prototype.splice;
var ArrayBufferIsView = ArrayBuffer.isView;

var NumberIsInteger = Number.isInteger;
var MathMax = Math.max;
var StringPrototypeIncludes = String.prototype.includes;
var Uint8ArrayPrototypeIncludes = Uint8Array.prototype.includes;

Expand Down Expand Up @@ -346,20 +347,23 @@ function execFile(file, args, options, callback) {
if (encoding) child_buffer.setEncoding(encoding);

let totalLen = 0;
let maxBufferTripped = false;
if (maxBuffer === Infinity) {
child_buffer.on("data", function onDataNoMaxBuf(chunk) {
$arrayPush(_buffer, chunk);
});
return;
}
child_buffer.on("data", function onData(chunk) {
if (maxBufferTripped) return;
const encoding = child_buffer.readableEncoding;
if (encoding) {
const length = Buffer.byteLength(chunk, encoding);
totalLen += length;

if (totalLen > maxBuffer) {
const truncatedLen = maxBuffer - (totalLen - length);
maxBufferTripped = true;
const truncatedLen = MathMax(0, maxBuffer - (totalLen - length));
$arrayPush(_buffer, String.prototype.slice.$call(chunk, 0, truncatedLen));

ex = $ERR_CHILD_PROCESS_STDIO_MAXBUFFER(kind);
Expand All @@ -372,7 +376,8 @@ function execFile(file, args, options, callback) {
totalLen += length;

if (totalLen > maxBuffer) {
const truncatedLen = maxBuffer - (totalLen - length);
maxBufferTripped = true;
const truncatedLen = MathMax(0, maxBuffer - (totalLen - length));
$arrayPush(_buffer, chunk.slice(0, truncatedLen));

ex = $ERR_CHILD_PROCESS_STDIO_MAXBUFFER(kind);
Expand Down
29 changes: 27 additions & 2 deletions test/js/node/child_process/child-process-exec.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, test } from "bun:test";
import { bunExe, isWindows } from "harness";
import { exec } from "node:child_process";
import { bunEnv, bunExe, isWindows } from "harness";
import { exec, execFile } from "node:child_process";

const SIZE = 262145;

Expand Down Expand Up @@ -104,6 +104,31 @@ describe.concurrent("child_process.exec", () => {
});
});

// Regression: a chunk already queued in the pipe when kill()/destroy() fires
// was being appended past maxBuffer because slice(0, negative) keeps a tail.
// Needs a writer that fills the pipe faster than the reader drains it; a
// debug-build Bun child starts too slowly to trigger it, so use head(1).
describe.concurrent.each(["buffer", "utf8"] as const)("maxBuffer cap with fast writer (%s)", enc => {
test.skipIf(isWindows)("stdout never exceeds maxBuffer", async () => {
const maxBuffer = 64 * 1024;
const results = await Promise.all(
Array.from({ length: 10 }, () => {
const { promise, resolve } = Promise.withResolvers<{ code: unknown; len: number }>();
execFile(
"head",
["-c", String(4 * 1024 * 1024), "/dev/zero"],
{ maxBuffer, encoding: enc, env: bunEnv },
(err, stdout) => resolve({ code: (err as NodeJS.ErrnoException | null)?.code, len: stdout.length }),
);
return promise;
}),
);
expect(results).toEqual(
Array.from({ length: 10 }, () => ({ code: "ERR_CHILD_PROCESS_STDIO_MAXBUFFER", len: maxBuffer })),
);
});
});

test.concurrent("exec with verbatim arguments", async () => {
const { resolve, reject, promise } = Promise.withResolvers();

Expand Down
Loading