Summary
boo kill (and boo kill --all) only signals the PTY's direct child — the shell — with SIGHUP. Grandchild processes that ignore/block SIGHUP, were backgrounded/daemonized, or escaped the shell's job table survive the kill and keep running with the session gone. They hold onto ports, files, etc., and a subsequent session started to replace them fails (e.g. Address already in use).
Mechanism
boo kill sends the quit control command, handled in src/daemon.zig:
} else if (std.mem.eql(u8, cmd, "quit")) {
self.retireListener();
conn.send(.ok, "");
if (self.win) |w| {
posix.kill(w.child_pid, posix.SIG.HUP) catch {}; // <-- positive pid: shell only
}
...
child_pid is the shell. A positive pid to kill(2) reaches exactly that one process. SIGHUP normally cascades to the shell's jobs via huponexit, so kill appears to work — until a job ignores SIGHUP or has re-parented/re-pgrp'd out, which is exactly what long-running servers and daemons tend to do. The session daemon exits, the listener socket is removed, but the orphaned grandchild keeps its resources.
The PTY child does call setsid() (src/pty.zig), so it is a session/process-group leader and child_pid == pgid.
Reproduction
- Start a session and run a SIGHUP-ignoring server bound to a fixed port:
boo new srv
# inside the session:
python3 -c 'import signal,socket; signal.signal(signal.SIGHUP, signal.SIG_IGN); \
s=socket.socket(); s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1); \
s.bind(("127.0.0.1",8000)); s.listen(1); print("up"); signal.pause()'
- From another terminal:
boo kill srv
- Observe the python process is still alive and still holds
127.0.0.1:8000:
ss -ltnp | grep :8000
ps -p $(pgrep -f 'signal.pause') -o pid,ppid,cmd
Its PPID is now 1 (reparented to init), confirming it escaped the session.
Expected: the whole process tree started by the session is torn down.
Actual: only the shell dies; the orphaned server survives.
Suggested fix
Signal the process group rather than the single pid:
posix.kill(-w.child_pid, posix.SIG.HUP) catch {}; // -pid == whole pgrp
Since child_pid == pgid (child runs setsid()), -child_pid targets every process in the session's group. This closes the common case (grandchildren still in the shell's group).
Honest limitation
A pgrp signal still cannot reach grandchildren that:
- were placed in their own process group by the shell's job control,
- called
setsid() themselves / double-forked into a new session,
- were started via
nohup/setsid/disown.
For a true full-tree kill, boo would need to enumerate descendants (/proc/*/stat PPids on Linux, proc_listchildpids on macOS) and signal each pid, escalating TERM → KILL. That is more code and two-platform, but it's the only thing that catches a daemonized grandchild. Worth deciding whether boo promises full-tree teardown or documents the same caveat tmux/screen live with.
Environment
- boo v0.6.3 (main @ 4af4bdf)
- Linux (repro), mechanism is platform-independent
Summary
boo kill(andboo kill --all) only signals the PTY's direct child — the shell — withSIGHUP. Grandchild processes that ignore/blockSIGHUP, were backgrounded/daemonized, or escaped the shell's job table survive the kill and keep running with the session gone. They hold onto ports, files, etc., and a subsequent session started to replace them fails (e.g.Address already in use).Mechanism
boo killsends thequitcontrol command, handled insrc/daemon.zig:child_pidis the shell. A positive pid tokill(2)reaches exactly that one process. SIGHUP normally cascades to the shell's jobs viahuponexit, sokillappears to work — until a job ignores SIGHUP or has re-parented/re-pgrp'd out, which is exactly what long-running servers and daemons tend to do. The session daemon exits, the listener socket is removed, but the orphaned grandchild keeps its resources.The PTY child does call
setsid()(src/pty.zig), so it is a session/process-group leader andchild_pid == pgid.Reproduction
boo kill srv127.0.0.1:8000:Expected: the whole process tree started by the session is torn down.
Actual: only the shell dies; the orphaned server survives.
Suggested fix
Signal the process group rather than the single pid:
Since
child_pid == pgid(child runssetsid()),-child_pidtargets every process in the session's group. This closes the common case (grandchildren still in the shell's group).Honest limitation
A pgrp signal still cannot reach grandchildren that:
setsid()themselves / double-forked into a new session,nohup/setsid/disown.For a true full-tree kill, boo would need to enumerate descendants (
/proc/*/statPPids on Linux,proc_listchildpidson macOS) and signal each pid, escalating TERM → KILL. That is more code and two-platform, but it's the only thing that catches a daemonized grandchild. Worth deciding whether boo promises full-tree teardown or documents the same caveat tmux/screen live with.Environment