mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
termio: use a pipe to notify the reader thread to quit
Simultaneously reading and closing a fd is UB. We need to ensure that we quit, then close.
This commit is contained in:
@ -139,6 +139,12 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
break :pid command.pid orelse return error.ProcessNoPid;
|
break :pid command.pid orelse return error.ProcessNoPid;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create our pipe that we'll use to kill our read thread.
|
||||||
|
// pipe[0] is the read end, pipe[1] is the write end.
|
||||||
|
const pipe = try std.os.pipe();
|
||||||
|
errdefer std.os.close(pipe[0]);
|
||||||
|
errdefer std.os.close(pipe[1]);
|
||||||
|
|
||||||
// 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);
|
||||||
@ -194,7 +200,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
const read_thread = try std.Thread.spawn(
|
const read_thread = try std.Thread.spawn(
|
||||||
.{},
|
.{},
|
||||||
ReadThread.threadMain,
|
ReadThread.threadMain,
|
||||||
.{ master_fd, ev_data_ptr },
|
.{ master_fd, ev_data_ptr, pipe[0] },
|
||||||
);
|
);
|
||||||
read_thread.setName("io-reader") catch {};
|
read_thread.setName("io-reader") catch {};
|
||||||
|
|
||||||
@ -203,6 +209,7 @@ pub fn threadEnter(self: *Exec, thread: *termio.Thread) !ThreadData {
|
|||||||
.alloc = alloc,
|
.alloc = alloc,
|
||||||
.ev = ev_data_ptr,
|
.ev = ev_data_ptr,
|
||||||
.read_thread = read_thread,
|
.read_thread = read_thread,
|
||||||
|
.read_thread_pipe = pipe[1],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,12 +217,15 @@ pub fn threadExit(self: *Exec, data: ThreadData) void {
|
|||||||
// Clear out our data since we're not active anymore.
|
// Clear out our data since we're not active anymore.
|
||||||
self.data = null;
|
self.data = null;
|
||||||
|
|
||||||
|
// Quit our read thread first so that we aren't simultaneously
|
||||||
|
// performing a read/close on the pty fd.
|
||||||
|
_ = std.os.write(data.read_thread_pipe, "x") catch |err|
|
||||||
|
log.warn("error writing to read thread quit pipe err={}", .{err});
|
||||||
|
data.read_thread.join();
|
||||||
|
|
||||||
// Stop our subprocess
|
// Stop our subprocess
|
||||||
if (data.ev.process_exited) self.subprocess.externalExit();
|
if (data.ev.process_exited) self.subprocess.externalExit();
|
||||||
self.subprocess.stop();
|
self.subprocess.stop();
|
||||||
|
|
||||||
// Wait for our reader thread to end
|
|
||||||
data.read_thread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update the configuration.
|
/// Update the configuration.
|
||||||
@ -338,8 +348,10 @@ const ThreadData = struct {
|
|||||||
|
|
||||||
/// Our read thread
|
/// Our read thread
|
||||||
read_thread: std.Thread,
|
read_thread: std.Thread,
|
||||||
|
read_thread_pipe: std.os.fd_t,
|
||||||
|
|
||||||
pub fn deinit(self: *ThreadData) void {
|
pub fn deinit(self: *ThreadData) void {
|
||||||
|
std.os.close(self.read_thread_pipe);
|
||||||
self.ev.deinit(self.alloc);
|
self.ev.deinit(self.alloc);
|
||||||
self.alloc.destroy(self.ev);
|
self.alloc.destroy(self.ev);
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
@ -934,11 +946,39 @@ const Subprocess = struct {
|
|||||||
/// This is also empirically fast compared to putting the read into
|
/// This is also empirically fast compared to putting the read into
|
||||||
/// an async mechanism like io_uring/epoll because the reads are generally
|
/// an async mechanism like io_uring/epoll because the reads are generally
|
||||||
/// small.
|
/// small.
|
||||||
|
///
|
||||||
|
/// We use a basic poll syscall here because we are only monitoring two
|
||||||
|
/// fds and this is still much faster and lower overhead than any async
|
||||||
|
/// mechanism.
|
||||||
const ReadThread = struct {
|
const ReadThread = struct {
|
||||||
/// The main entrypoint for the thread.
|
/// The main entrypoint for the thread.
|
||||||
fn threadMain(fd: std.os.fd_t, ev: *EventData) void {
|
fn threadMain(fd: std.os.fd_t, ev: *EventData, quit: std.os.fd_t) void {
|
||||||
|
// Always close our end of the pipe when we exit.
|
||||||
|
defer std.os.close(quit);
|
||||||
|
|
||||||
|
// Build up the list of fds we're going to poll. We are looking
|
||||||
|
// for data on the pty and our quit notification.
|
||||||
|
var pollfds: [2]std.os.pollfd = .{
|
||||||
|
.{ .fd = fd, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
|
.{ .fd = quit, .events = std.os.POLL.IN, .revents = undefined },
|
||||||
|
};
|
||||||
|
|
||||||
var buf: [1024]u8 = undefined;
|
var buf: [1024]u8 = undefined;
|
||||||
while (true) {
|
while (true) {
|
||||||
|
// Wait for data.
|
||||||
|
_ = std.os.poll(&pollfds, 0) catch |err| {
|
||||||
|
log.warn("poll failed on read thread, exiting early err={}", .{err});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
// If our quit fd is set, we're done.
|
||||||
|
if (pollfds[1].revents & std.os.POLL.IN != 0) {
|
||||||
|
log.info("read thread got quit signal", .{});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure our pty has data.
|
||||||
|
if (pollfds[0].revents & std.os.POLL.IN == 0) continue;
|
||||||
const n = std.os.read(fd, &buf) catch |err| {
|
const n = std.os.read(fd, &buf) catch |err| {
|
||||||
switch (err) {
|
switch (err) {
|
||||||
// This means our pty is closed. We're probably
|
// This means our pty is closed. We're probably
|
||||||
|
Reference in New Issue
Block a user