aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-09-25 09:19:09 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2022-09-25 09:19:09 -0700
commitd2b81fa7c974159307c7dae86a1c9313e1cc2e05 (patch)
tree82bbeb98b9c157984701e6a17c2c16d23d1b9ced
parenta8ab899816d4ddb16b56727bef7998d38179390e (diff)
downloadbun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.tar.gz
bun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.tar.zst
bun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.zip
Linux implementation
-rw-r--r--src/bun.js/api/bun.zig112
-rw-r--r--src/bun.js/api/bun/spawn.zig15
-rw-r--r--src/bun.js/bindings/bindings.zig7
-rw-r--r--src/bun.js/event_loop.zig11
-rw-r--r--src/bun.js/node/syscall.zig1
-rw-r--r--src/linux_c.zig57
6 files changed, 180 insertions, 23 deletions
diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig
index 739449f3f..58918b18b 100644
--- a/src/bun.js/api/bun.zig
+++ b/src/bun.js/api/bun.zig
@@ -3375,6 +3375,10 @@ pub const Subprocess = struct {
pub usingnamespace JSC.Codegen.JSSubprocess;
pid: std.os.pid_t,
+ // on macOS, this is nothing
+ // on linux, it's a pidfd
+ pidfd: std.os.fd_t = std.math.maxInt(std.os.fd_t),
+
stdin: Writable,
stdout: Readable,
stderr: Readable,
@@ -3386,10 +3390,12 @@ pub const Subprocess = struct {
this_jsvalue: JSValue = JSValue.zero,
exit_code: ?u8 = null,
+ waitpid_err: ?JSC.Node.Syscall.Error = null,
has_waitpid_task: bool = false,
notification_task: JSC.AnyTask = undefined,
waitpid_task: JSC.AnyTask = undefined,
+
wait_task: JSC.ConcurrentTask = .{},
finalized: bool = false,
@@ -3497,9 +3503,25 @@ pub const Subprocess = struct {
return JSValue.jsUndefined();
}
- const err = std.c.kill(this.pid, sig);
- if (err != 0) {
- return JSC.Node.Syscall.Error.fromCode(std.c.getErrno(err), .kill).toJSC(globalThis);
+ if (comptime Environment.isLinux) {
+ // should this be handled differently?
+ // this effectively shouldn't happen
+ if (this.pidfd == std.math.maxInt(std.os.fd_t)) {
+ return JSValue.jsUndefined();
+ }
+
+ // first appeared in Linux 5.1
+ const rc = std.os.linux.pidfd_send_signal(this.pidfd, @intCast(u8, sig), null, 0);
+
+ if (rc != 0) {
+ globalThis.throwValue(JSC.Node.Syscall.Error.fromCode(std.os.linux.getErrno(rc), .kill).toJSC(globalThis));
+ return JSValue.jsUndefined();
+ }
+ } else {
+ const err = std.c.kill(this.pid, sig);
+ if (err != 0) {
+ return JSC.Node.Syscall.Error.fromCode(std.c.getErrno(err), .kill).toJSC(globalThis);
+ }
}
return JSValue.jsUndefined();
@@ -3517,8 +3539,14 @@ pub const Subprocess = struct {
}
pub fn closePorts(this: *Subprocess) void {
+ if (comptime Environment.isLinux) {
+ if (this.pidfd != std.math.maxInt(std.os.fd_t)) {
+ _ = std.os.close(this.pidfd);
+ this.pidfd = std.math.maxInt(std.os.fd_t);
+ }
+ }
+
if (this.stdout == .pipe) {
- this.stdout.pipe.isLocked()
this.stdout.pipe.cancel(this.globalThis);
}
@@ -3801,9 +3829,15 @@ pub const Subprocess = struct {
defer attr.deinit();
var actions = PosixSpawn.Actions.init() catch |err| return globalThis.handleError(err, "in posix_spawn");
- attr.set(
- os.darwin.POSIX_SPAWN_CLOEXEC_DEFAULT | os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK,
- ) catch |err| return globalThis.handleError(err, "in posix_spawn");
+ if (comptime Environment.isMac) {
+ attr.set(
+ os.darwin.POSIX_SPAWN_CLOEXEC_DEFAULT | os.darwin.POSIX_SPAWN_SETSIGDEF | os.darwin.POSIX_SPAWN_SETSIGMASK,
+ ) catch |err| return globalThis.handleError(err, "in posix_spawn");
+ } else if (comptime Environment.isLinux) {
+ attr.set(
+ bun.C.linux.POSIX_SPAWN.SETSIGDEF | bun.C.linux.POSIX_SPAWN.SETSIGMASK,
+ ) catch |err| return globalThis.handleError(err, "in posix_spawn");
+ }
defer actions.deinit();
if (env_array.items.len == 0) {
@@ -3882,6 +3916,36 @@ pub const Subprocess = struct {
.result => |pid_| pid_,
};
+ const pidfd: std.os.fd_t = brk: {
+ if (Environment.isMac) {
+ break :brk @intCast(std.os.fd_t, pid);
+ }
+
+ const kernel = @import("analytics").GenerateHeader.GeneratePlatform.kernelVersion();
+
+ // pidfd_nonblock only supported in 5.10+
+ const flags: u32 = if (kernel.orderWithoutTag(.{ .major = 5, .minor = 10, .patch = 0 }).compare(.gte))
+ std.os.O.NONBLOCK
+ else
+ 0;
+
+ const fd = std.os.linux.pidfd_open(
+ pid,
+ flags,
+ );
+
+ switch (std.os.linux.getErrno(fd)) {
+ .SUCCESS => break :brk fd,
+ else => |err| {
+ globalThis.throwValue(JSC.Node.Syscall.Error.fromCode(err, .open).toJSC(globalThis));
+ var status: u32 = 0;
+ // ensure we don't leak the child process on error
+ _ = std.os.linux.waitpid(pid, &status, 0);
+ return JSValue.jsUndefined();
+ },
+ }
+ };
+
var subprocess = globalThis.allocator().create(Subprocess) catch {
globalThis.throw("out of memory", .{});
return JSValue.jsUndefined();
@@ -3890,6 +3954,7 @@ pub const Subprocess = struct {
subprocess.* = Subprocess{
.globalThis = globalThis,
.pid = pid,
+ .pidfd = pidfd,
.stdin = Writable.init(std.meta.activeTag(stdio[std.os.STDIN_FILENO]), stdin_pipe[1], globalThis) catch {
globalThis.throw("out of memory", .{});
return JSValue.jsUndefined();
@@ -3902,7 +3967,7 @@ pub const Subprocess = struct {
subprocess.this_jsvalue.ensureStillAlive();
switch (globalThis.bunVM().poller.watch(
- @intCast(JSC.Node.FileDescriptor, pid),
+ @intCast(JSC.Node.FileDescriptor, pidfd),
.process,
Subprocess,
subprocess,
@@ -3937,13 +4002,17 @@ pub const Subprocess = struct {
this.has_waitpid_task = true;
const pid = this.pid;
- const status = PosixSpawn.waitpid(pid, 0) catch |err| {
- Output.debug("waitpid({d}) failed: {s}", .{ pid, @errorName(err) });
- return;
- };
+ switch (PosixSpawn.waitpid(pid, 0)) {
+ .err => |err| {
+ this.waitpid_err = err;
+ },
+ .result => |status| {
+ this.exit_code = @truncate(u8, status.status);
+ },
+ }
- this.exit_code = @truncate(u8, status.status);
this.waitpid_task = JSC.AnyTask.New(Subprocess, onExit).init(this);
+ this.has_waitpid_task = true;
vm.eventLoop().enqueueTask(JSC.Task.init(&this.waitpid_task));
}
@@ -3955,7 +4024,16 @@ pub const Subprocess = struct {
if (this.exit_promise != .zero) {
var promise = this.exit_promise;
this.exit_promise = .zero;
- promise.asPromise().?.resolve(this.globalThis, JSValue.jsNumber(this.exit_code.?));
+ if (this.exit_code) |code| {
+ promise.asPromise().?.resolve(this.globalThis, JSValue.jsNumber(code));
+ } else if (this.waitpid_err) |err| {
+ this.waitpid_err = null;
+ promise.asPromise().?.reject(this.globalThis, err.toJSC(this.globalThis));
+ } else {
+ // crash in debug mode
+ if (comptime Environment.allow_assert)
+ unreachable;
+ }
}
this.unref();
@@ -4010,7 +4088,11 @@ pub const Subprocess = struct {
try actions.open(std_fileno, pathlike.slice(), flag | std.os.O.CREAT, 0o664);
},
.inherit => {
- try actions.inherit(std_fileno);
+ if (comptime Environment.isMac) {
+ try actions.inherit(std_fileno);
+ } else {
+ try actions.dup2(std_fileno, std_fileno);
+ }
},
.ignore => {
const flag = if (std_fileno == std.os.STDIN_FILENO) @as(u32, os.O.RDONLY) else @as(u32, std.os.O.WRONLY);
diff --git a/src/bun.js/api/bun/spawn.zig b/src/bun.js/api/bun/spawn.zig
index 95ce7fa73..bbc5d4fac 100644
--- a/src/bun.js/api/bun/spawn.zig
+++ b/src/bun.js/api/bun/spawn.zig
@@ -236,20 +236,21 @@ pub const PosixSpawn = struct {
/// or `posix_spawnp` syscalls.
/// See also `std.os.waitpid` for an alternative if your child process was spawned via `fork` and
/// `execve` method.
- pub fn waitpid(pid: pid_t, flags: u32) !WaitPidResult {
+ pub fn waitpid(pid: pid_t, flags: u32) Maybe(WaitPidResult) {
const Status = c_int;
var status: Status = undefined;
while (true) {
const rc = system.waitpid(pid, &status, @intCast(c_int, flags));
switch (errno(rc)) {
- .SUCCESS => return WaitPidResult{
- .pid = @intCast(pid_t, rc),
- .status = @bitCast(u32, status),
+ .SUCCESS => return Maybe(WaitPidResult){
+ .result = .{
+ .pid = @intCast(pid_t, rc),
+ .status = @bitCast(u32, status),
+ },
},
.INTR => continue,
- .CHILD => return error.ChildExecFailed,
- .INVAL => unreachable, // Invalid flags.
- else => unreachable,
+
+ else => return JSC.Maybe(WaitPidResult).errnoSys(rc, .waitpid).?,
}
}
}
diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig
index 3b41c9fa4..f352fd0ac 100644
--- a/src/bun.js/bindings/bindings.zig
+++ b/src/bun.js/bindings/bindings.zig
@@ -1896,6 +1896,13 @@ pub const JSGlobalObject = extern struct {
}
}
+ pub fn throwValue(
+ this: *JSGlobalObject,
+ value: JSC.JSValue,
+ ) void {
+ this.vm().throwError(this, value);
+ }
+
pub fn throwError(
this: *JSGlobalObject,
err: anyerror,
diff --git a/src/bun.js/event_loop.zig b/src/bun.js/event_loop.zig
index 1d011c509..238b3907f 100644
--- a/src/bun.js/event_loop.zig
+++ b/src/bun.js/event_loop.zig
@@ -491,6 +491,15 @@ pub const Poller = struct {
loop.active -= 1;
loader.onPoll(0, 0);
},
+ @field(Pollable.Tag, "Subprocess") => {
+ var loader = ptr.as(JSC.Subprocess);
+
+ loop.num_polls -= 1;
+
+ // kqueue sends the same notification multiple times in the same tick potentially
+ // so we have to dedupe it
+ _ = loader.globalThis.bunVM().eventLoop().pending_processes_to_exit.getOrPut(loader) catch unreachable;
+ },
else => unreachable,
}
}
@@ -522,7 +531,7 @@ pub const Poller = struct {
if (comptime Environment.isLinux) {
const flags: u32 = switch (flag) {
- .read => linux.EPOLL.IN | linux.EPOLL.HUP | linux.EPOLL.ONESHOT,
+ .process, .read => linux.EPOLL.IN | linux.EPOLL.HUP | linux.EPOLL.ONESHOT,
.write => linux.EPOLL.OUT | linux.EPOLL.HUP | linux.EPOLL.ERR | linux.EPOLL.ONESHOT,
};
diff --git a/src/bun.js/node/syscall.zig b/src/bun.js/node/syscall.zig
index 2af73ef21..d677a4510 100644
--- a/src/bun.js/node/syscall.zig
+++ b/src/bun.js/node/syscall.zig
@@ -100,6 +100,7 @@ pub const Tag = enum(u8) {
kqueue,
epoll_ctl,
kill,
+ waitpid,
pub var strings = std.EnumMap(Tag, JSC.C.JSStringRef).initFull(null);
};
const PathString = @import("../../global.zig").PathString;
diff --git a/src/linux_c.zig b/src/linux_c.zig
index 5a0e320b0..5a3acad9f 100644
--- a/src/linux_c.zig
+++ b/src/linux_c.zig
@@ -375,3 +375,60 @@ pub fn get_release(name_buffer: *[std.os.HOST_NAME_MAX]u8) []const u8 {
return name_buffer[0..result.len];
}
+
+// Taken from spawn.h header
+pub const POSIX_SPAWN = struct {
+ pub const RESETIDS = 0x01;
+ pub const SETPGROUP = 0x02;
+ pub const SETSIGDEF = 0x04;
+ pub const SETSIGMASK = 0x08;
+ pub const SETSCHEDPARAM = 0x10;
+ pub const SETSCHEDULER = 0x20;
+ pub const USEVFORK = 0x40;
+ pub const SETSID = 0x80;
+};
+
+pub const posix_spawnattr_t = *opaque {};
+pub const posix_spawn_file_actions_t = *opaque {};
+pub extern "c" fn posix_spawnattr_init(attr: *posix_spawnattr_t) c_int;
+pub extern "c" fn posix_spawnattr_destroy(attr: *posix_spawnattr_t) void;
+pub extern "c" fn posix_spawnattr_setflags(attr: *posix_spawnattr_t, flags: c_short) c_int;
+pub extern "c" fn posix_spawnattr_getflags(attr: *const posix_spawnattr_t, flags: *c_short) c_int;
+pub extern "c" fn posix_spawn_file_actions_init(actions: *posix_spawn_file_actions_t) c_int;
+pub extern "c" fn posix_spawn_file_actions_destroy(actions: *posix_spawn_file_actions_t) void;
+pub extern "c" fn posix_spawn_file_actions_addclose(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
+pub extern "c" fn posix_spawn_file_actions_addopen(
+ actions: *posix_spawn_file_actions_t,
+ filedes: fd_t,
+ path: [*:0]const u8,
+ oflag: c_int,
+ mode: mode_t,
+) c_int;
+pub extern "c" fn posix_spawn_file_actions_adddup2(
+ actions: *posix_spawn_file_actions_t,
+ filedes: fd_t,
+ newfiledes: fd_t,
+) c_int;
+pub extern "c" fn posix_spawn_file_actions_addfchdir_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
+
+// not available in linux
+// pub extern "c" fn posix_spawn_file_actions_addinherit_np(actions: *posix_spawn_file_actions_t, filedes: fd_t) c_int;
+
+pub extern "c" fn posix_spawn_file_actions_addchdir_np(actions: *posix_spawn_file_actions_t, path: [*:0]const u8) c_int;
+
+pub extern "c" fn posix_spawn(
+ pid: *pid_t,
+ path: [*:0]const u8,
+ actions: ?*const posix_spawn_file_actions_t,
+ attr: ?*const posix_spawnattr_t,
+ argv: [*:null]?[*:0]const u8,
+ env: [*:null]?[*:0]const u8,
+) c_int;
+pub extern "c" fn posix_spawnp(
+ pid: *pid_t,
+ path: [*:0]const u8,
+ actions: ?*const posix_spawn_file_actions_t,
+ attr: ?*const posix_spawnattr_t,
+ argv: [*:null]?[*:0]const u8,
+ env: [*:null]?[*:0]const u8,
+) c_int;