diff options
author | 2022-09-25 09:19:09 -0700 | |
---|---|---|
committer | 2022-09-25 09:19:09 -0700 | |
commit | d2b81fa7c974159307c7dae86a1c9313e1cc2e05 (patch) | |
tree | 82bbeb98b9c157984701e6a17c2c16d23d1b9ced | |
parent | a8ab899816d4ddb16b56727bef7998d38179390e (diff) | |
download | bun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.tar.gz bun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.tar.zst bun-d2b81fa7c974159307c7dae86a1c9313e1cc2e05.zip |
Linux implementation
-rw-r--r-- | src/bun.js/api/bun.zig | 112 | ||||
-rw-r--r-- | src/bun.js/api/bun/spawn.zig | 15 | ||||
-rw-r--r-- | src/bun.js/bindings/bindings.zig | 7 | ||||
-rw-r--r-- | src/bun.js/event_loop.zig | 11 | ||||
-rw-r--r-- | src/bun.js/node/syscall.zig | 1 | ||||
-rw-r--r-- | src/linux_c.zig | 57 |
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; |