Skip to content

Commit 84fed9e

Browse files
committed
Fix TTY reader resource ownership
1 parent 8e4afa6 commit 84fed9e

3 files changed

Lines changed: 21 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.4.5 (2026-04-25)
4+
5+
### Fixed
6+
7+
- Fixed TTY NIF reader thread ownership by storing the thread and closed flag in the resource payload instead of a copied payload
8+
39
## 0.4.4 (2026-04-25)
410

511
### Testing

lib/ghostty/terminal/ghostty_nif.zig

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const TtyData = struct {
3333
original: c.struct_termios,
3434
owner_pid: beam.pid,
3535
closed: std.atomic.Value(bool),
36+
thread: ?std.Thread,
3637
};
3738

3839
pub const TtyResource = beam.Resource(TtyData, root, .{
@@ -73,9 +74,14 @@ fn tty_wait_for_input(fd: c_int, timeout_ms: c_int) c_short {
7374
}
7475
}
7576

76-
fn tty_close(data: anytype) void {
77-
const closed_ptr = @constCast(&data.closed);
78-
if (closed_ptr.swap(true, .acq_rel)) return;
77+
fn tty_close(data: *TtyData) void {
78+
if (data.closed.swap(true, .acq_rel)) return;
79+
80+
if (data.thread) |thread| {
81+
thread.join();
82+
data.thread = null;
83+
}
84+
7985
_ = c.tcsetattr(data.fd, c.TCSANOW, &data.original);
8086
_ = c.close(data.fd);
8187
if (data.write_fd != data.fd) _ = c.close(data.write_fd);
@@ -172,19 +178,18 @@ pub fn nif_tty_open(owner: beam.pid, signals: bool) !TtyResource {
172178
.original = original,
173179
.owner_pid = owner,
174180
.closed = std.atomic.Value(bool).init(false),
181+
.thread = null,
175182
}, .{});
176183

177-
const data = res.unpack();
178-
const closed_ptr = @constCast(&data.closed);
179-
const thread = std.Thread.spawn(.{}, tty_reader_loop, .{ fd, owner, closed_ptr }) catch
184+
const tty = res.__payload;
185+
tty.thread = std.Thread.spawn(.{}, tty_reader_loop, .{ fd, owner, &tty.closed }) catch
180186
return error.thread_spawn_failed;
181-
thread.detach();
182187

183188
return res;
184189
}
185190

186191
pub fn nif_tty_write(res: TtyResource, data: []const u8) void {
187-
const tty = res.unpack();
192+
const tty = res.__payload;
188193
if (tty.closed.load(.acquire)) return;
189194

190195
var off: usize = 0;
@@ -202,7 +207,7 @@ pub fn nif_tty_write(res: TtyResource, data: []const u8) void {
202207
}
203208

204209
pub fn nif_tty_close(res: TtyResource) void {
205-
tty_close(res.unpack());
210+
tty_close(res.__payload);
206211
}
207212

208213
fn on_write_pty(terminal: g.GhosttyTerminal, userdata: ?*anyopaque, data_ptr: [*c]const u8, len: usize) callconv(.c) void {

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule Ghostty.MixProject do
22
use Mix.Project
33

4-
@version "0.4.4"
4+
@version "0.4.5"
55
@source_url "https://github.com/dannote/ghostty_ex"
66

77
def project do

0 commit comments

Comments
 (0)