- commit
- 377c062
- parent
- 6e6bc9c
- author
- Eric Bower
- date
- 2026-01-20 16:03:54 -0500 EST
fix: gracefully shutdown daemon process This change properly handles when the daemon process receives a SIGTERM. Also during the kill process we properly forward SIGTERM to all children processes of the daemon.
1 files changed,
+31,
-11
+31,
-11
1@@ -55,6 +55,7 @@ else
2 c.forkpty;
3
4 var sigwinch_received: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);
5+var sigterm_received: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);
6
7 const Client = struct {
8 alloc: std.mem.Allocator,
9@@ -241,7 +242,8 @@ const Daemon = struct {
10
11 pub fn handleKill(self: *Daemon) void {
12 std.log.info("kill received session={s}", .{self.session_name});
13- posix.kill(self.pid, posix.SIG.TERM) catch |err| {
14+ // negative pid means kill process and children
15+ posix.kill(-self.pid, posix.SIG.TERM) catch |err| {
16 std.log.warn("failed to send SIGTERM to pty child err={s}", .{@errorName(err)});
17 };
18 self.shutdown();
19@@ -680,6 +682,7 @@ fn ensureSession(daemon: *Daemon) !EnsureSessionResult {
20 };
21 }
22 try daemonLoop(daemon, server_sock_fd, pty_fd);
23+ daemon.handleKill();
24 _ = posix.waitpid(daemon.pid, 0);
25 daemon.deinit();
26 return .{ .created = true, .is_daemon = true };
27@@ -1007,7 +1010,7 @@ fn clientLoop(_: *Cfg, client_sock_fd: i32) !void {
28
29 fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
30 std.log.info("daemon started session={s} pty_fd={d}", .{ daemon.session_name, pty_fd });
31- var should_exit = false;
32+ setupSigtermHandler();
33 var poll_fds = try std.ArrayList(posix.pollfd).initCapacity(daemon.alloc, 8);
34 defer poll_fds.deinit(daemon.alloc);
35
36@@ -1021,7 +1024,12 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
37 var vt_stream = term.vtStream();
38 defer vt_stream.deinit();
39
40- while (!should_exit and daemon.running) {
41+ daemon_loop: while (daemon.running) {
42+ if (sigterm_received.swap(false, .acq_rel)) {
43+ std.log.info("SIGTERM received, shutting down gracefully session={s}", .{daemon.session_name});
44+ break :daemon_loop;
45+ }
46+
47 poll_fds.clearRetainingCapacity();
48
49 try poll_fds.append(daemon.alloc, .{
50@@ -1054,7 +1062,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
51
52 if (poll_fds.items[0].revents & (posix.POLL.ERR | posix.POLL.HUP | posix.POLL.NVAL) != 0) {
53 std.log.err("server socket error revents={d}", .{poll_fds.items[0].revents});
54- should_exit = true;
55+ break :daemon_loop;
56 } else if (poll_fds.items[0].revents & posix.POLL.IN != 0) {
57 const client_fd = try posix.accept(server_sock_fd, null, null, posix.SOCK.NONBLOCK | posix.SOCK.CLOEXEC);
58 const client = try daemon.alloc.create(Client);
59@@ -1081,7 +1089,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
60 if (n == 0) {
61 // EOF: Shell exited
62 std.log.info("shell exited pty_fd={d}", .{pty_fd});
63- should_exit = true;
64+ break :daemon_loop;
65 } else {
66 // Feed PTY output to terminal emulator for state tracking
67 try vt_stream.nextSlice(buf[0..n]);
68@@ -1119,14 +1127,14 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
69 if (err == error.WouldBlock) continue;
70 std.log.debug("client read err={s} fd={d}", .{ @errorName(err), client.socket_fd });
71 const last = daemon.closeClient(client, i, false);
72- if (last) should_exit = true;
73+ if (last) break :daemon_loop;
74 continue;
75 };
76
77 if (n == 0) {
78 // Client closed connection
79 const last = daemon.closeClient(client, i, false);
80- if (last) should_exit = true;
81+ if (last) break :daemon_loop;
82 continue;
83 }
84
85@@ -1145,8 +1153,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
86 },
87 .Kill => {
88 daemon.handleKill();
89- should_exit = true;
90- break :clients_loop;
91+ break :daemon_loop;
92 },
93 .Info => try daemon.handleInfo(client),
94 .History => try daemon.handleHistory(client, &term, msg.payload),
95@@ -1162,7 +1169,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
96 if (err == error.WouldBlock) break :blk 0;
97 // Error on write, close client
98 const last = daemon.closeClient(client, i, false);
99- if (last) should_exit = true;
100+ if (last) break :daemon_loop;
101 continue;
102 };
103
104@@ -1177,7 +1184,7 @@ fn daemonLoop(daemon: *Daemon, server_sock_fd: i32, pty_fd: i32) !void {
105
106 if (revents & (posix.POLL.HUP | posix.POLL.ERR | posix.POLL.NVAL) != 0) {
107 const last = daemon.closeClient(client, i, false);
108- if (last) should_exit = true;
109+ if (last) break :daemon_loop;
110 }
111 }
112 }
113@@ -1334,6 +1341,10 @@ fn handleSigwinch(_: i32, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c
114 sigwinch_received.store(true, .release);
115 }
116
117+fn handleSigterm(_: i32, _: *const posix.siginfo_t, _: ?*anyopaque) callconv(.c) void {
118+ sigterm_received.store(true, .release);
119+}
120+
121 fn setupSigwinchHandler() void {
122 const act: posix.Sigaction = .{
123 .handler = .{ .sigaction = handleSigwinch },
124@@ -1343,6 +1354,15 @@ fn setupSigwinchHandler() void {
125 posix.sigaction(posix.SIG.WINCH, &act, null);
126 }
127
128+fn setupSigtermHandler() void {
129+ const act: posix.Sigaction = .{
130+ .handler = .{ .sigaction = handleSigterm },
131+ .mask = posix.sigemptyset(),
132+ .flags = posix.SA.SIGINFO,
133+ };
134+ posix.sigaction(posix.SIG.TERM, &act, null);
135+}
136+
137 fn getTerminalSize(fd: i32) ipc.Resize {
138 var ws: c.struct_winsize = undefined;
139 if (c.ioctl(fd, c.TIOCGWINSZ, &ws) == 0 and ws.ws_row > 0 and ws.ws_col > 0) {