mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-15 08:16:13 +03:00
termio: IO reader thread cleanup works
This commit is contained in:
@ -439,15 +439,14 @@ pub fn create(alloc: Allocator, app: *App, config: *const Config) !*Window {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Window) void {
|
pub fn destroy(self: *Window) void {
|
||||||
|
// Stop rendering thread
|
||||||
{
|
{
|
||||||
// Stop rendering thread
|
|
||||||
self.renderer_thread.stop.notify() catch |err|
|
self.renderer_thread.stop.notify() catch |err|
|
||||||
log.err("error notifying renderer thread to stop, may stall err={}", .{err});
|
log.err("error notifying renderer thread to stop, may stall err={}", .{err});
|
||||||
self.renderer_thr.join();
|
self.renderer_thr.join();
|
||||||
|
|
||||||
// We need to become the active rendering thread again
|
// We need to become the active rendering thread again
|
||||||
self.renderer.threadEnter(self.window) catch unreachable;
|
self.renderer.threadEnter(self.window) catch unreachable;
|
||||||
self.renderer_thread.deinit();
|
|
||||||
|
|
||||||
// If we are devmode-owning, clean that up.
|
// If we are devmode-owning, clean that up.
|
||||||
if (DevMode.enabled and DevMode.instance.window == self) {
|
if (DevMode.enabled and DevMode.instance.window == self) {
|
||||||
@ -460,22 +459,22 @@ pub fn destroy(self: *Window) void {
|
|||||||
// Uninitialize imgui
|
// Uninitialize imgui
|
||||||
self.imgui_ctx.destroy();
|
self.imgui_ctx.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deinit our renderer
|
|
||||||
self.renderer.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stop our IO thread
|
||||||
{
|
{
|
||||||
// Stop our IO thread
|
|
||||||
self.io_thread.stop.notify() catch |err|
|
self.io_thread.stop.notify() catch |err|
|
||||||
log.err("error notifying io thread to stop, may stall err={}", .{err});
|
log.err("error notifying io thread to stop, may stall err={}", .{err});
|
||||||
self.io_thr.join();
|
self.io_thr.join();
|
||||||
self.io_thread.deinit();
|
|
||||||
|
|
||||||
// Deinitialize our terminal IO
|
|
||||||
self.io.deinit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We need to deinit AFTER everything is stopped, since there are
|
||||||
|
// shared values between the two threads.
|
||||||
|
self.renderer_thread.deinit();
|
||||||
|
self.renderer.deinit();
|
||||||
|
self.io_thread.deinit();
|
||||||
|
self.io.deinit();
|
||||||
|
|
||||||
self.window.deinit();
|
self.window.deinit();
|
||||||
|
|
||||||
self.font_group.deinit(self.alloc);
|
self.font_group.deinit(self.alloc);
|
||||||
|
@ -29,10 +29,7 @@ const c = @cImport({
|
|||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
|
|
||||||
/// This is the pty fd created for the subcommand.
|
/// This is the pty fd created for the subcommand.
|
||||||
pty: Pty,
|
subprocess: Subprocess,
|
||||||
|
|
||||||
/// This is the container for the subcommand.
|
|
||||||
command: Command,
|
|
||||||
|
|
||||||
/// The terminal emulator internal state. This is the abstract "terminal"
|
/// The terminal emulator internal state. This is the abstract "terminal"
|
||||||
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
/// that manages input, grid updating, etc. and is renderer-agnostic. It
|
||||||
@ -62,77 +59,9 @@ grid_size: renderer.GridSize,
|
|||||||
/// The data associated with the currently running thread.
|
/// The data associated with the currently running thread.
|
||||||
data: ?*EventData,
|
data: ?*EventData,
|
||||||
|
|
||||||
/// The thread that is started to read the data associated with the pty.
|
|
||||||
read_thread: ?std.Thread,
|
|
||||||
|
|
||||||
/// Initialize the exec implementation. This will also start the child
|
/// Initialize the exec implementation. This will also start the child
|
||||||
/// process.
|
/// process.
|
||||||
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
||||||
// Create our pty
|
|
||||||
var pty = try Pty.open(.{
|
|
||||||
.ws_row = @intCast(u16, opts.grid_size.rows),
|
|
||||||
.ws_col = @intCast(u16, opts.grid_size.columns),
|
|
||||||
.ws_xpixel = @intCast(u16, opts.screen_size.width),
|
|
||||||
.ws_ypixel = @intCast(u16, opts.screen_size.height),
|
|
||||||
});
|
|
||||||
errdefer pty.deinit();
|
|
||||||
|
|
||||||
// Determine the path to the binary we're executing
|
|
||||||
const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse
|
|
||||||
return error.CommandNotFound;
|
|
||||||
defer alloc.free(path);
|
|
||||||
|
|
||||||
// Set our env vars
|
|
||||||
var env = try std.process.getEnvMap(alloc);
|
|
||||||
defer env.deinit();
|
|
||||||
try env.put("TERM", "xterm-256color");
|
|
||||||
try env.put("COLORTERM", "truecolor");
|
|
||||||
|
|
||||||
// On macOS, we launch the program as a login shell. This is a Mac-specific
|
|
||||||
// behavior (see other terminals). Terminals in general should NOT be
|
|
||||||
// spawning login shells because well... we're not "logging in." The solution
|
|
||||||
// is to put dotfiles in "rc" variants rather than "_login" variants. But,
|
|
||||||
// history!
|
|
||||||
var argv0_buf: []u8 = undefined;
|
|
||||||
const args: []const []const u8 = if (comptime builtin.target.isDarwin()) args: {
|
|
||||||
// Get rid of the path
|
|
||||||
const argv0 = if (std.mem.lastIndexOf(u8, path, "/")) |idx|
|
|
||||||
path[idx + 1 ..]
|
|
||||||
else
|
|
||||||
path;
|
|
||||||
|
|
||||||
// Copy it with a hyphen so its a login shell
|
|
||||||
argv0_buf = try alloc.alloc(u8, argv0.len + 1);
|
|
||||||
argv0_buf[0] = '-';
|
|
||||||
std.mem.copy(u8, argv0_buf[1..], argv0);
|
|
||||||
break :args &[_][]const u8{argv0_buf};
|
|
||||||
} else &[_][]const u8{path};
|
|
||||||
|
|
||||||
// We can free the args buffer since it is only used for start.
|
|
||||||
// cmd retains a dangling pointer but we're not supposed to access it.
|
|
||||||
defer if (comptime builtin.target.isDarwin()) alloc.free(argv0_buf);
|
|
||||||
|
|
||||||
// Build our subcommand
|
|
||||||
var cmd: Command = .{
|
|
||||||
.path = path,
|
|
||||||
.args = args,
|
|
||||||
.env = &env,
|
|
||||||
.cwd = opts.config.@"working-directory",
|
|
||||||
.stdin = .{ .handle = pty.slave },
|
|
||||||
.stdout = .{ .handle = pty.slave },
|
|
||||||
.stderr = .{ .handle = pty.slave },
|
|
||||||
.pre_exec = (struct {
|
|
||||||
fn callback(cmd: *Command) void {
|
|
||||||
const p = cmd.getData(Pty) orelse unreachable;
|
|
||||||
p.childPreExec() catch |err|
|
|
||||||
log.err("error initializing child: {}", .{err});
|
|
||||||
}
|
|
||||||
}).callback,
|
|
||||||
.data = &pty,
|
|
||||||
};
|
|
||||||
try cmd.start(alloc);
|
|
||||||
log.info("started subcommand path={s} pid={?}", .{ path, cmd.pid });
|
|
||||||
|
|
||||||
// Create our terminal
|
// Create our terminal
|
||||||
var term = try terminal.Terminal.init(
|
var term = try terminal.Terminal.init(
|
||||||
alloc,
|
alloc,
|
||||||
@ -142,82 +71,45 @@ pub fn init(alloc: Allocator, opts: termio.Options) !Exec {
|
|||||||
errdefer term.deinit(alloc);
|
errdefer term.deinit(alloc);
|
||||||
term.color_palette = opts.config.palette.value;
|
term.color_palette = opts.config.palette.value;
|
||||||
|
|
||||||
|
var subprocess = try Subprocess.init(alloc, opts);
|
||||||
|
errdefer subprocess.deinit();
|
||||||
|
|
||||||
return Exec{
|
return Exec{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.pty = pty,
|
|
||||||
.command = cmd,
|
|
||||||
.terminal = term,
|
.terminal = term,
|
||||||
.terminal_stream = undefined,
|
.terminal_stream = undefined,
|
||||||
|
.subprocess = subprocess,
|
||||||
.renderer_state = opts.renderer_state,
|
.renderer_state = opts.renderer_state,
|
||||||
.renderer_wakeup = opts.renderer_wakeup,
|
.renderer_wakeup = opts.renderer_wakeup,
|
||||||
.renderer_mailbox = opts.renderer_mailbox,
|
.renderer_mailbox = opts.renderer_mailbox,
|
||||||
.window_mailbox = opts.window_mailbox,
|
.window_mailbox = opts.window_mailbox,
|
||||||
.grid_size = opts.grid_size,
|
.grid_size = opts.grid_size,
|
||||||
.data = null,
|
.data = null,
|
||||||
.read_thread = null,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Exec) void {
|
pub fn deinit(self: *Exec) void {
|
||||||
// Kill our command
|
self.subprocess.deinit(self.alloc);
|
||||||
self.killCommand() catch |err|
|
|
||||||
log.err("error sending SIGHUP to command, may hang: {}", .{err});
|
|
||||||
_ = self.command.wait(false) catch |err|
|
|
||||||
log.err("error waiting for command to exit: {}", .{err});
|
|
||||||
|
|
||||||
// Wait for our reader thread to end
|
|
||||||
if (self.read_thread) |thr| thr.join();
|
|
||||||
|
|
||||||
// Clean up our other members
|
// Clean up our other members
|
||||||
self.terminal.deinit(self.alloc);
|
self.terminal.deinit(self.alloc);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Kill the underlying subprocess. This closes the pty file handle and
|
|
||||||
/// sends a SIGHUP to the child process. This doesn't wait for the child
|
|
||||||
/// process to be exited.
|
|
||||||
fn killCommand(self: *Exec) !void {
|
|
||||||
// Close our PTY
|
|
||||||
self.pty.deinit();
|
|
||||||
|
|
||||||
// We need to get our process group ID and send a SIGHUP to it.
|
|
||||||
if (self.command.pid) |pid| {
|
|
||||||
const pgid_: ?c.pid_t = pgid: {
|
|
||||||
const pgid = c.getpgid(pid);
|
|
||||||
|
|
||||||
// Don't know why it would be zero but its not a valid pid
|
|
||||||
if (pgid == 0) break :pgid null;
|
|
||||||
|
|
||||||
// If the pid doesn't exist then... okay.
|
|
||||||
if (pgid == c.ESRCH) break :pgid null;
|
|
||||||
|
|
||||||
// If we have an error...
|
|
||||||
if (pgid < 0) {
|
|
||||||
log.warn("error getting pgid for kill", .{});
|
|
||||||
break :pgid null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break :pgid pgid;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (pgid_) |pgid| {
|
|
||||||
if (c.killpg(pgid, c.SIGHUP) < 0) {
|
|
||||||
log.warn("error killing process group pgid={}", .{pgid});
|
|
||||||
return error.KillFailed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
||||||
assert(self.data == null);
|
assert(self.data == null);
|
||||||
const alloc = self.alloc;
|
const alloc = self.alloc;
|
||||||
|
|
||||||
|
// Start our subprocess
|
||||||
|
try self.subprocess.start(alloc);
|
||||||
|
errdefer self.subprocess.stop();
|
||||||
|
const master_fd = self.subprocess.pty.?.master;
|
||||||
|
|
||||||
// Setup our data that is used for callbacks
|
// Setup our data that is used for callbacks
|
||||||
var ev_data_ptr = try alloc.create(EventData);
|
var ev_data_ptr = try alloc.create(EventData);
|
||||||
errdefer alloc.destroy(ev_data_ptr);
|
errdefer alloc.destroy(ev_data_ptr);
|
||||||
|
|
||||||
// Setup our stream so that we can write.
|
// Setup our stream so that we can write.
|
||||||
var stream = xev.Stream.initFd(self.pty.master);
|
var stream = xev.Stream.initFd(master_fd);
|
||||||
errdefer stream.deinit();
|
errdefer stream.deinit();
|
||||||
|
|
||||||
// Wakeup watcher for the writer thread.
|
// Wakeup watcher for the writer thread.
|
||||||
@ -249,24 +141,30 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
self.data = ev_data_ptr;
|
self.data = ev_data_ptr;
|
||||||
|
|
||||||
// Start our reader thread
|
// Start our reader thread
|
||||||
assert(self.read_thread == null);
|
const read_thread = try std.Thread.spawn(
|
||||||
self.read_thread = try std.Thread.spawn(
|
|
||||||
.{},
|
.{},
|
||||||
ReadThread.threadMain,
|
ReadThread.threadMain,
|
||||||
.{ self.pty.master, ev_data_ptr },
|
.{ master_fd, ev_data_ptr },
|
||||||
);
|
);
|
||||||
self.read_thread.?.setName("io-reader") catch {};
|
read_thread.setName("io-reader") catch {};
|
||||||
|
|
||||||
// Return our thread data
|
// Return our thread data
|
||||||
return ThreadData{
|
return ThreadData{
|
||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.ev = ev_data_ptr,
|
.ev = ev_data_ptr,
|
||||||
|
.read_thread = read_thread,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn threadExit(self: *Exec, data: ThreadData) void {
|
pub fn threadExit(self: *Exec, data: ThreadData) void {
|
||||||
_ = data;
|
// Clear out our data since we're not active anymore.
|
||||||
self.data = null;
|
self.data = null;
|
||||||
|
|
||||||
|
// Stop our subprocess
|
||||||
|
self.subprocess.stop();
|
||||||
|
|
||||||
|
// Wait for our reader thread to end
|
||||||
|
data.read_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resize the terminal.
|
/// Resize the terminal.
|
||||||
@ -276,15 +174,9 @@ pub fn resize(
|
|||||||
screen_size: renderer.ScreenSize,
|
screen_size: renderer.ScreenSize,
|
||||||
padding: renderer.Padding,
|
padding: renderer.Padding,
|
||||||
) !void {
|
) !void {
|
||||||
const padded_size = screen_size.subPadding(padding);
|
|
||||||
|
|
||||||
// Update the size of our pty
|
// Update the size of our pty
|
||||||
try self.pty.setSize(.{
|
const padded_size = screen_size.subPadding(padding);
|
||||||
.ws_row = @intCast(u16, grid_size.rows),
|
try self.subprocess.resize(grid_size, padded_size);
|
||||||
.ws_col = @intCast(u16, grid_size.columns),
|
|
||||||
.ws_xpixel = @intCast(u16, padded_size.width),
|
|
||||||
.ws_ypixel = @intCast(u16, padded_size.height),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Update our cached grid size
|
// Update our cached grid size
|
||||||
self.grid_size = grid_size;
|
self.grid_size = grid_size;
|
||||||
@ -331,6 +223,9 @@ const ThreadData = struct {
|
|||||||
/// The data that is attached to the callbacks.
|
/// The data that is attached to the callbacks.
|
||||||
ev: *EventData,
|
ev: *EventData,
|
||||||
|
|
||||||
|
/// Our read thread
|
||||||
|
read_thread: std.Thread,
|
||||||
|
|
||||||
pub fn deinit(self: *ThreadData) void {
|
pub fn deinit(self: *ThreadData) void {
|
||||||
self.ev.deinit(self.alloc);
|
self.ev.deinit(self.alloc);
|
||||||
self.alloc.destroy(self.ev);
|
self.alloc.destroy(self.ev);
|
||||||
@ -422,6 +317,175 @@ fn ttyWrite(
|
|||||||
return .disarm;
|
return .disarm;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Subprocess = struct {
|
||||||
|
cwd: ?[]const u8,
|
||||||
|
env: std.process.EnvMap,
|
||||||
|
path: []const u8,
|
||||||
|
argv0_override: ?[]const u8,
|
||||||
|
grid_size: renderer.GridSize,
|
||||||
|
screen_size: renderer.ScreenSize,
|
||||||
|
|
||||||
|
pty: ?Pty = null,
|
||||||
|
command: ?Command = null,
|
||||||
|
|
||||||
|
pub fn init(alloc: Allocator, opts: termio.Options) !Subprocess {
|
||||||
|
// Determine the path to the binary we're executing
|
||||||
|
const path = (try Command.expandPath(alloc, opts.config.command orelse "sh")) orelse
|
||||||
|
return error.CommandNotFound;
|
||||||
|
errdefer alloc.free(path);
|
||||||
|
|
||||||
|
// On macOS, we launch the program as a login shell. This is a Mac-specific
|
||||||
|
// behavior (see other terminals). Terminals in general should NOT be
|
||||||
|
// spawning login shells because well... we're not "logging in." The solution
|
||||||
|
// is to put dotfiles in "rc" variants rather than "_login" variants. But,
|
||||||
|
// history!
|
||||||
|
const argv0_override: ?[]const u8 = if (comptime builtin.target.isDarwin()) argv0: {
|
||||||
|
// Get rid of the path
|
||||||
|
const argv0 = if (std.mem.lastIndexOf(u8, path, "/")) |idx|
|
||||||
|
path[idx + 1 ..]
|
||||||
|
else
|
||||||
|
path;
|
||||||
|
|
||||||
|
// Copy it with a hyphen so its a login shell
|
||||||
|
const argv0_buf = try alloc.alloc(u8, argv0.len + 1);
|
||||||
|
argv0_buf[0] = '-';
|
||||||
|
std.mem.copy(u8, argv0_buf[1..], argv0);
|
||||||
|
break :argv0 argv0_buf;
|
||||||
|
} else null;
|
||||||
|
errdefer if (argv0_override) |buf| alloc.free(buf);
|
||||||
|
|
||||||
|
// Set our env vars
|
||||||
|
var env = try std.process.getEnvMap(alloc);
|
||||||
|
errdefer env.deinit();
|
||||||
|
try env.put("TERM", "xterm-256color");
|
||||||
|
try env.put("COLORTERM", "truecolor");
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.env = env,
|
||||||
|
.cwd = opts.config.@"working-directory",
|
||||||
|
.path = path,
|
||||||
|
.argv0_override = argv0_override,
|
||||||
|
.grid_size = opts.grid_size,
|
||||||
|
.screen_size = opts.screen_size,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Subprocess, alloc: Allocator) void {
|
||||||
|
self.stop();
|
||||||
|
self.env.deinit();
|
||||||
|
alloc.free(self.path);
|
||||||
|
if (self.argv0_override) |v| alloc.free(v);
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(self: *Subprocess, alloc: Allocator) !void {
|
||||||
|
// Create our pty
|
||||||
|
var pty = try Pty.open(.{
|
||||||
|
.ws_row = @intCast(u16, self.grid_size.rows),
|
||||||
|
.ws_col = @intCast(u16, self.grid_size.columns),
|
||||||
|
.ws_xpixel = @intCast(u16, self.screen_size.width),
|
||||||
|
.ws_ypixel = @intCast(u16, self.screen_size.height),
|
||||||
|
});
|
||||||
|
self.pty = pty;
|
||||||
|
errdefer {
|
||||||
|
pty.deinit();
|
||||||
|
self.pty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = &[_][]const u8{self.argv0_override orelse self.path};
|
||||||
|
|
||||||
|
// Build our subcommand
|
||||||
|
var cmd: Command = .{
|
||||||
|
.path = self.path,
|
||||||
|
.args = args,
|
||||||
|
.env = &self.env,
|
||||||
|
.cwd = self.cwd,
|
||||||
|
.stdin = .{ .handle = pty.slave },
|
||||||
|
.stdout = .{ .handle = pty.slave },
|
||||||
|
.stderr = .{ .handle = pty.slave },
|
||||||
|
.pre_exec = (struct {
|
||||||
|
fn callback(cmd: *Command) void {
|
||||||
|
const p = cmd.getData(Pty) orelse unreachable;
|
||||||
|
p.childPreExec() catch |err|
|
||||||
|
log.err("error initializing child: {}", .{err});
|
||||||
|
}
|
||||||
|
}).callback,
|
||||||
|
.data = &self.pty.?,
|
||||||
|
};
|
||||||
|
try cmd.start(alloc);
|
||||||
|
errdefer killCommand(cmd);
|
||||||
|
log.info("started subcommand path={s} pid={?}", .{ self.path, cmd.pid });
|
||||||
|
|
||||||
|
self.command = cmd;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(self: *Subprocess) void {
|
||||||
|
// Close our PTY
|
||||||
|
if (self.pty) |*pty| {
|
||||||
|
pty.deinit();
|
||||||
|
self.pty = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kill our command
|
||||||
|
if (self.command) |*cmd| {
|
||||||
|
killCommand(cmd) catch |err|
|
||||||
|
log.err("error sending SIGHUP to command, may hang: {}", .{err});
|
||||||
|
_ = cmd.wait(false) catch |err|
|
||||||
|
log.err("error waiting for command to exit: {}", .{err});
|
||||||
|
self.command = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(
|
||||||
|
self: *Subprocess,
|
||||||
|
grid_size: renderer.GridSize,
|
||||||
|
screen_size: renderer.ScreenSize,
|
||||||
|
) !void {
|
||||||
|
self.grid_size = grid_size;
|
||||||
|
self.screen_size = screen_size;
|
||||||
|
|
||||||
|
if (self.pty) |pty| {
|
||||||
|
try pty.setSize(.{
|
||||||
|
.ws_row = @intCast(u16, grid_size.rows),
|
||||||
|
.ws_col = @intCast(u16, grid_size.columns),
|
||||||
|
.ws_xpixel = @intCast(u16, screen_size.width),
|
||||||
|
.ws_ypixel = @intCast(u16, screen_size.height),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Kill the underlying subprocess. This sends a SIGHUP to the child
|
||||||
|
/// process. This doesn't wait for the child process to be exited.
|
||||||
|
fn killCommand(command: *Command) !void {
|
||||||
|
if (command.pid) |pid| {
|
||||||
|
const pgid_: ?c.pid_t = pgid: {
|
||||||
|
const pgid = c.getpgid(pid);
|
||||||
|
|
||||||
|
// Don't know why it would be zero but its not a valid pid
|
||||||
|
if (pgid == 0) break :pgid null;
|
||||||
|
|
||||||
|
// If the pid doesn't exist then... okay.
|
||||||
|
if (pgid == c.ESRCH) break :pgid null;
|
||||||
|
|
||||||
|
// If we have an error...
|
||||||
|
if (pgid < 0) {
|
||||||
|
log.warn("error getting pgid for kill", .{});
|
||||||
|
break :pgid null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :pgid pgid;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (pgid_) |pgid| {
|
||||||
|
if (c.killpg(pgid, c.SIGHUP) < 0) {
|
||||||
|
log.warn("error killing process group pgid={}", .{pgid});
|
||||||
|
return error.KillFailed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/// The read thread sits in a loop doing the following pseudo code:
|
/// The read thread sits in a loop doing the following pseudo code:
|
||||||
///
|
///
|
||||||
/// while (true) { blocking_read(); exit_if_eof(); process(); }
|
/// while (true) { blocking_read(); exit_if_eof(); process(); }
|
||||||
@ -440,11 +504,17 @@ const ReadThread = struct {
|
|||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
const n = std.os.read(fd, &buf) catch |err| {
|
const n = std.os.read(fd, &buf) catch |err| {
|
||||||
log.err("READ ERROR err={}", .{err});
|
switch (err) {
|
||||||
|
// This means our pty is closed. We're probably
|
||||||
|
// gracefully shutting down.
|
||||||
|
error.NotOpenForReading => log.info("io reader exiting", .{}),
|
||||||
|
|
||||||
|
else => log.err("READ ERROR err={}", .{err}),
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
log.info("DATA: {d}", .{n});
|
|
||||||
|
|
||||||
|
// log.info("DATA: {d}", .{n});
|
||||||
@call(.always_inline, process, .{ ev, buf[0..n] });
|
@call(.always_inline, process, .{ ev, buf[0..n] });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user