mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
Fix shell crash in load test (#18320)
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
17
test/js/bun/shell/shell-immediate-exit-fixture.js
generated
Normal file
17
test/js/bun/shell/shell-immediate-exit-fixture.js
generated
Normal 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);
|
||||
9
test/js/bun/shell/shell-load.test.ts
Normal file
9
test/js/bun/shell/shell-load.test.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user