Compare commits

...

6 Commits

Author SHA1 Message Date
Jarred Sumner
c086e62bd8 Merge branch 'main' into jarred/shell-ref 2024-07-10 22:08:50 -07:00
Jarred Sumner
2e251e390e Add hack 2024-06-22 15:13:34 -07:00
Jarred Sumner
c6da7aba56 Merge branch 'main' into jarred/shell-ref 2024-06-22 14:50:46 -07:00
Jarred Sumner
4fdb3e6825 Merge branch 'main' into jarred/shell-ref 2024-06-19 00:54:05 -07:00
Jarred Sumner
63e3b26564 Update interpreter.zig 2024-06-18 14:42:05 -07:00
Jarred Sumner
e7340f440b Drain microtasks in the Bun Shell 2024-06-18 13:59:27 -07:00
5 changed files with 83 additions and 2 deletions

View File

@@ -51,6 +51,7 @@ const ast = shell.AST;
const SmolList = shell.SmolList;
const GlobWalker = Glob.GlobWalker_(null, Glob.SyscallAccessor, true);
const isWindows = bun.Environment.isWindows;
const stdin_no = 0;
const stdout_no = 1;
@@ -818,6 +819,48 @@ pub const ParsedShellScript = struct {
}
};
// TODO: remove this hack
// We need to force libuv to enter it's event loop
// So we use a timer that never fires to keep the event loop running
const WindowsKeepAlive = struct {
handle: ?*uv.Timer = null,
pub fn ref(this: *WindowsKeepAlive, event_loop: *uv.Loop) void {
var handle = this.handle orelse brk: {
this.handle = init(event_loop);
break :brk this.handle.?;
};
handle.start(std.math.maxInt(i64) - 1, 0, callback);
handle.ref();
}
pub const activate = ref;
fn init(event_loop: *uv.Loop) *uv.Timer {
var handle = bun.new(uv.Timer, std.mem.zeroes(uv.Timer));
handle.init(event_loop);
handle.data = null;
return handle;
}
pub fn disable(this: *WindowsKeepAlive) void {
if (this.handle) |handle| {
this.handle = null;
handle.stop();
uv.uv_close(@alignCast(@ptrCast(handle)), onClose);
}
}
fn onClose(ptr: ?*anyopaque) callconv(.C) void {
const timer: *uv.Timer = @alignCast(@ptrCast(ptr.?));
bun.destroy(timer);
}
fn callback(_: *uv.Timer) callconv(.C) void {
bun.Output.panic("WindowsKeepAlive timer should never fire", .{});
}
};
/// This interpreter works by basically turning the AST into a state machine so
/// that execution can be suspended and resumed to support async.
pub const Interpreter = struct {
@@ -843,6 +886,7 @@ pub const Interpreter = struct {
async_commands_executing: u32 = 0,
globalThis: *JSC.JSGlobalObject,
ref: if (isWindows) WindowsKeepAlive else bun.Async.KeepAlive = .{},
flags: packed struct(u8) {
done: bool = false,
@@ -1235,7 +1279,7 @@ pub const Interpreter = struct {
interpreter.this_jsvalue = JSC.Codegen.JSShellInterpreter.toJS(interpreter, globalThis);
JSC.Codegen.JSShellInterpreter.resolveSetCached(interpreter.this_jsvalue, globalThis, resolve);
JSC.Codegen.JSShellInterpreter.rejectSetCached(interpreter.this_jsvalue, globalThis, reject);
interpreter.ref.activate(JSC.VirtualMachine.get().uvLoop());
bun.Analytics.Features.shell += 1;
return interpreter.this_jsvalue;
}
@@ -1660,6 +1704,8 @@ pub const Interpreter = struct {
defer decrPendingActivityFlag(&this.has_pending_activity);
if (this.event_loop == .js) {
this.ref.disable();
defer this.deinitAfterJSRun();
this.exit_code = exit_code;
if (this.this_jsvalue != .zero) {
@@ -1680,6 +1726,8 @@ pub const Interpreter = struct {
defer decrPendingActivityFlag(&this.has_pending_activity);
if (this.event_loop == .js) {
this.ref.disable();
if (this.this_jsvalue != .zero) {
if (JSC.Codegen.JSShellInterpreter.rejectGetCached(this.this_jsvalue)) |reject| {
reject.call(this.globalThis, &[_]JSValue{ JSValue.jsNumberFromChar(1), this.getBufferedStdout(), this.getBufferedStderr() });
@@ -1696,6 +1744,7 @@ pub const Interpreter = struct {
for (this.jsobjs) |jsobj| {
jsobj.unprotect();
}
this.ref.disable();
this.root_io.deref();
this.root_shell.deinitImpl(false, false);
this.this_jsvalue = .zero;
@@ -1709,6 +1758,7 @@ pub const Interpreter = struct {
this.root_shell._buffered_stdout.owned.deinitWithAllocator(bun.default_allocator);
}
this.this_jsvalue = .zero;
this.ref.disable();
this.allocator.destroy(this);
}

View File

@@ -949,7 +949,15 @@ pub const ShellSubprocess = struct {
if (exit_code) |code| {
if (this.cmd_parent) |cmd| {
if (cmd.exit_code == null) {
cmd.onExit(code);
if (this.event_loop == .js) {
const loop = this.event_loop.js;
loop.enter();
defer loop.exit();
cmd.onExit(code);
} else {
cmd.onExit(code);
}
}
}
}

View File

@@ -0,0 +1,6 @@
process.exitCode = 1;
(async () => {
await Bun.$`ls .`;
process.exit(0);
})();

View File

@@ -0,0 +1,6 @@
process.exitCode = 1;
(async () => {
await Bun.$`${process.execPath} -e "console.log('hi')"`;
process.exit(0);
})();

View File

@@ -0,0 +1,11 @@
import { expect, test } from "bun:test";
import { join } from "path";
import "harness";
test("shell should stay alive while a builtin command is in progress", async () => {
expect([join(import.meta.dir, "shell-keepalive-fixture-1.js")]).toRun();
});
test("shell should stay alive while a non-builtin command is in progress", async () => {
expect([join(import.meta.dir, "shell-keepalive-fixture-2.js")]).toRun();
});