mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary Fixes two critical bugs in Bun Shell: 1. **Memory leaks & incorrect GC reporting**: Shell objects weren't reporting their memory usage to JavaScriptCore's garbage collector, causing memory to accumulate unchecked. Also fixes a leak where `ShellArgs` wasn't being freed in `Interpreter.finalize()`. 2. **Blocking I/O on macOS**: Fixes a bug where writing large amounts of data (>1MB) to pipes would block the main thread on macOS. The issue: `sendto()` with `MSG_NOWAIT` flag blocks on macOS despite the flag, so we now avoid the socket fast path unless the socket is already non-blocking. ## Changes - Adds `memoryCost()` and `estimatedSize()` implementations across shell AST nodes, interpreter, and I/O structures - Reports estimated memory size to JavaScriptCore GC via `vm.heap.reportExtraMemoryAllocated()` - Fixes missing `this.args.deinit()` call in interpreter finalization - Fixes `BabyList.memoryCost()` to return bytes, not element count - Conditionally uses socket fast path in IOWriter based on platform and socket state ## Test plan - [x] New test: `shell-leak-args.test.ts` - validates memory doesn't leak during parsing/execution - [x] New test: `shell-blocking-pipe.test.ts` - validates large pipe writes don't block the main thread - [x] Existing shell tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Claude Bot <claude-bot@bun.sh>
69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
import { $ } from "bun";
|
|
import { expect, test } from "bun:test";
|
|
|
|
test("shell parsing error does not leak emmory", async () => {
|
|
const buffer = Buffer.alloc(1024 * 1024, "A").toString();
|
|
for (let i = 0; i < 5; i++) {
|
|
try {
|
|
$`${{ raw: buffer }} <!INVALID ==== SYNTAX!>`;
|
|
} catch (e) {}
|
|
}
|
|
const rss = process.memoryUsage.rss();
|
|
for (let i = 0; i < 200; i++) {
|
|
try {
|
|
$`${{ raw: buffer }} <!INVALID ==== SYNTAX!>`;
|
|
} catch (e) {}
|
|
}
|
|
const after = process.memoryUsage.rss() / 1024 / 1024;
|
|
const before = rss / 1024 / 1024;
|
|
// In Bun v1.3.0 on macOS arm64:
|
|
// Expected: < 100
|
|
// Received: 524.65625
|
|
// In Bun v1.3.1 on macOS arm64:
|
|
// Expected: < 100
|
|
// Received: 0.25
|
|
expect(after - before).toBeLessThan(100);
|
|
});
|
|
|
|
test("shell execution doesn't leak argv", async () => {
|
|
const buffer = Buffer.alloc(1024 * 1024, "bun!").toString();
|
|
const cmd = `echo ${buffer}`;
|
|
for (let i = 0; i < 5; i++) {
|
|
await $`${{ raw: cmd }}`.quiet();
|
|
}
|
|
const rss = process.memoryUsage.rss();
|
|
for (let i = 0; i < 200; i++) {
|
|
await $`${{ raw: cmd }}`.quiet();
|
|
}
|
|
const after = process.memoryUsage.rss() / 1024 / 1024;
|
|
const before = rss / 1024 / 1024;
|
|
// In Bun v1.3.0 on macOS arm64:
|
|
// Expected: < 250
|
|
// Received: 588.515625
|
|
// In Bun v1.3.1 on macOS arm64:
|
|
// Expected: < 250
|
|
// Received: 93.875
|
|
expect(after - before).toBeLessThan(250);
|
|
});
|
|
|
|
test("non-awaited shell command does not leak argv", async () => {
|
|
const buffer = Buffer.alloc(1024 * 1024, "bun!").toString();
|
|
const cmd = `echo ${buffer}`;
|
|
for (let i = 0; i < 5; i++) {
|
|
$`${{ raw: cmd }}`.quiet();
|
|
}
|
|
const rss = process.memoryUsage.rss();
|
|
for (let i = 0; i < 200; i++) {
|
|
$`${{ raw: cmd }}`.quiet();
|
|
}
|
|
const after = process.memoryUsage.rss() / 1024 / 1024;
|
|
const before = rss / 1024 / 1024;
|
|
// In Bun v1.3.0 on macOS arm64:
|
|
// Expected: < 250
|
|
// Received: 588.515625
|
|
// In Bun v1.3.1 on macOS arm64:
|
|
// Expected: < 250
|
|
// Received: 93.875
|
|
expect(after - before).toBeLessThan(250);
|
|
});
|