Fix shell crash in load test (#18320)

This commit is contained in:
Jarred Sumner
2025-03-20 11:36:43 -07:00
committed by GitHub
parent 27cf0d5eaf
commit da9c980d26
4 changed files with 49 additions and 19 deletions

View File

@@ -4931,8 +4931,8 @@ pub const Interpreter = struct {
.child = undefined,
.buffered_closed = buffered_closed,
} };
const subproc = switch (Subprocess.spawnAsync(this.base.eventLoop(), &shellio, spawn_args, &this.exec.subproc.child)) {
// FIXME: There's a race condition where this could change variants before spawnAsync returns.
var did_exit_immediately = false;
const subproc = switch (Subprocess.spawnAsync(this.base.eventLoop(), &shellio, spawn_args, &this.exec.subproc.child, &did_exit_immediately)) {
.result => this.exec.subproc.child,
.err => |*e| {
this.exec = .none;
@@ -4943,6 +4943,17 @@ pub const Interpreter = struct {
subproc.ref();
this.spawn_arena_freed = true;
arena.deinit();
if (did_exit_immediately) {
if (subproc.process.hasExited()) {
// process has already exited, we called wait4(), but we did not call onProcessExit()
subproc.process.onExit(subproc.process.status, &std.mem.zeroes(bun.spawn.Rusage));
} else {
// process has already exited, but we haven't called wait4() yet
// https://cs.github.com/libuv/libuv/blob/b00d1bd225b602570baee82a6152eaa823a84fa6/src/unix/process.c#L1007
subproc.process.wait(false);
}
}
}
fn setStdioFromRedirect(stdio: *[3]shell.subproc.Stdio, flags: ast.RedirectFlags, val: shell.subproc.Stdio) void {
@@ -10591,9 +10602,9 @@ pub const Interpreter = struct {
if (err == .sys and
err.sys.getErrno() == .BUSY and
(task.tgt_absolute != null and
err.sys.path.eqlUTF8(task.tgt_absolute.?)) or
err.sys.path.eqlUTF8(task.tgt_absolute.?)) or
(task.src_absolute != null and
err.sys.path.eqlUTF8(task.src_absolute.?)))
err.sys.path.eqlUTF8(task.src_absolute.?)))
{
log("{} got ebusy {d} {d}", .{ this, this.state.exec.ebusy.tasks.items.len, this.state.exec.paths_to_copy.len });
this.state.exec.ebusy.tasks.append(bun.default_allocator, task) catch bun.outOfMemory();

View File

@@ -746,6 +746,7 @@ pub const ShellSubprocess = struct {
shellio: *ShellIO,
spawn_args_: SpawnArgs,
out: **@This(),
notify_caller_process_already_exited: *bool,
) bun.shell.Result(void) {
var arena = bun.ArenaAllocator.init(bun.default_allocator);
defer arena.deinit();
@@ -761,6 +762,7 @@ pub const ShellSubprocess = struct {
&spawn_args,
shellio,
out,
notify_caller_process_already_exited,
)) {
.result => |subproc| subproc,
.err => |err| return .{ .err = err },
@@ -778,6 +780,7 @@ pub const ShellSubprocess = struct {
spawn_args: *SpawnArgs,
shellio: *ShellIO,
out_subproc: **@This(),
notify_caller_process_already_exited: *bool,
) bun.shell.Result(*@This()) {
const is_sync = config.is_sync;
@@ -876,26 +879,16 @@ pub const ShellSubprocess = struct {
subprocess.stdin.pipe.signal = JSC.WebCore.Signal.init(&subprocess.stdin);
}
var send_exit_notification = false;
if (comptime !is_sync) {
switch (subprocess.process.watch()) {
.result => {},
.err => {
send_exit_notification = true;
notify_caller_process_already_exited.* = true;
spawn_args.lazy = false;
},
}
}
defer {
if (send_exit_notification) {
// process has already exited
// https://cs.github.com/libuv/libuv/blob/b00d1bd225b602570baee82a6152eaa823a84fa6/src/unix/process.c#L1007
subprocess.wait(subprocess.flags.is_sync);
}
}
if (subprocess.stdin == .buffer) {
subprocess.stdin.buffer.start().assert();
}
@@ -1128,10 +1121,10 @@ pub const PipeReader = struct {
if (Environment.isWindows) {
this.reader.source =
switch (result) {
.buffer => .{ .pipe = this.stdio_result.buffer },
.buffer_fd => .{ .file = bun.io.Source.openFile(this.stdio_result.buffer_fd) },
.unavailable => @panic("Shouldn't happen."),
};
.buffer => .{ .pipe = this.stdio_result.buffer },
.buffer_fd => .{ .file = bun.io.Source.openFile(this.stdio_result.buffer_fd) },
.unavailable => @panic("Shouldn't happen."),
};
}
this.reader.setParent(this);

View File

@@ -0,0 +1,17 @@
import { $, which } from "bun";
const cat = which("cat");
const promises = [];
for (let j = 0; j < 500; j++) {
for (let i = 0; i < 100; i++) {
promises.push($`${cat} ${import.meta.path}`.text().then(() => {}));
}
if (j % 10 === 0) {
await Promise.all(promises);
promises.length = 0;
console.count("Ran");
}
}
await Promise.all(promises);

View File

@@ -0,0 +1,9 @@
import { describe, test, expect } from "bun:test";
import { isCI, isWindows } from "harness";
import path from "path";
describe("shell load", () => {
// windows process spawning is a lot slower
test.skipIf(isCI && isWindows)("immediate exit", () => {
expect([path.join(import.meta.dir, "./shell-immediate-exit-fixture.js")]).toRun();
});
});