mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-14 07:46:12 +03:00
termio/exec: fix 100% CPU usage after wait-after-command process exits (#4171)
read(2) returning 0 means that the other end of the pipe/pty has been closed (EOF), so there cannot be any more output to read on the pipe, and the io reader thread can just exit. If exec.wait_after_command=false, the read thread's quit pipe is immediately written to after the child process dies, so all is well. However, if wait_after_command=true (which is the case when using Ghostty to run a .command/.sh file on macOS), the read thread keeps spinning, causing persistent 100% CPU usage per exited process. Fix it by exiting the reader thread on EOF.
This commit is contained in:
@ -179,8 +179,11 @@ pub fn threadExit(self: *Exec, td: *termio.Termio.ThreadData) void {
|
||||
// Quit our read thread after exiting the subprocess so that
|
||||
// we don't get stuck waiting for data to stop flowing if it is
|
||||
// a particularly noisy process.
|
||||
_ = posix.write(exec.read_thread_pipe, "x") catch |err|
|
||||
log.warn("error writing to read thread quit pipe err={}", .{err});
|
||||
if (exec.read_thread_pipe) |pipe| {
|
||||
posix.close(pipe);
|
||||
// Tell deinit that we've already closed the pipe
|
||||
exec.read_thread_pipe = null;
|
||||
}
|
||||
|
||||
if (comptime builtin.os.tag == .windows) {
|
||||
// Interrupt the blocking read so the thread can see the quit message
|
||||
@ -639,7 +642,7 @@ pub const ThreadData = struct {
|
||||
|
||||
/// Reader thread state
|
||||
read_thread: std.Thread,
|
||||
read_thread_pipe: posix.fd_t,
|
||||
read_thread_pipe: ?posix.fd_t,
|
||||
read_thread_fd: posix.fd_t,
|
||||
|
||||
/// The timer to detect termios state changes.
|
||||
@ -652,7 +655,8 @@ pub const ThreadData = struct {
|
||||
termios_mode: ptypkg.Mode = .{},
|
||||
|
||||
pub fn deinit(self: *ThreadData, alloc: Allocator) void {
|
||||
posix.close(self.read_thread_pipe);
|
||||
// If the pipe isn't closed, close it.
|
||||
if (self.read_thread_pipe) |pipe| posix.close(pipe);
|
||||
|
||||
// Clear our write pools. We know we aren't ever going to do
|
||||
// any more IO since we stop our data stream below so we can just
|
||||
@ -1433,9 +1437,12 @@ pub const ReadThread = struct {
|
||||
};
|
||||
|
||||
// This happens on macOS instead of WouldBlock when the
|
||||
// child process dies. To be safe, we just break the loop
|
||||
// and let our poll happen.
|
||||
if (n == 0) break;
|
||||
// child process dies. It's equivalent to NotOpenForReading
|
||||
// so we can just exit.
|
||||
if (n == 0) {
|
||||
log.info("io reader exiting", .{});
|
||||
return;
|
||||
}
|
||||
|
||||
// log.info("DATA: {d}", .{n});
|
||||
@call(.always_inline, termio.Termio.processOutput, .{ io, buf[0..n] });
|
||||
@ -1447,8 +1454,8 @@ pub const ReadThread = struct {
|
||||
return;
|
||||
};
|
||||
|
||||
// If our quit fd is set, we're done.
|
||||
if (pollfds[1].revents & posix.POLL.IN != 0) {
|
||||
// If our quit fd is closed, we're done.
|
||||
if (pollfds[1].revents & posix.POLL.HUP != 0) {
|
||||
log.info("read thread got quit signal", .{});
|
||||
return;
|
||||
}
|
||||
|
Reference in New Issue
Block a user