From 7adbcb79ee8f4f5c9e38dd06fd7d7f5a49ea651d Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Wed, 4 Feb 2026 22:06:49 +1300 Subject: [PATCH] Use a process group for isolation. --- lib/async/container/forked.rb | 14 +++++++++++--- releases.md | 4 ++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/async/container/forked.rb b/lib/async/container/forked.rb index 7cde0ba..0c49f73 100644 --- a/lib/async/container/forked.rb +++ b/lib/async/container/forked.rb @@ -100,6 +100,11 @@ def self.fork(**options) # $stderr.puts fork: caller self.new(**options) do |process| ::Process.fork do + # Create a new process group for this child process. + # This prevents Ctrl-C from the terminal from directly interrupting child processes. + # Instead, only the controller receives the interrupt and can gracefully shut down children. + ::Process.setpgid(0, 0) + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. Signal.trap(:INT){::Thread.current.raise(Interrupt)} Signal.trap(:TERM){::Thread.current.raise(Interrupt)} # Same as SIGINT. @@ -214,10 +219,11 @@ def terminate! end end - # Send `SIGKILL` to the child process. + # Send `SIGKILL` to the child process and its entire process group. + # This ensures any subprocesses spawned by the child are also killed. def kill! unless @status - ::Process.kill(:KILL, @pid) + ::Process.kill(:KILL, -@pid) end end @@ -248,7 +254,9 @@ def wait(timeout = 0.1) Console.warn(self, "Process is blocking, sending kill signal...", child: {process_id: @pid}, timeout: timeout) self.kill! - # Wait for the process to exit: + # Wait for the direct child process to exit. + # Any subprocesses in the process group are also killed by SIGKILL, + # and when the child exits, its subprocesses are reparented to init which reaps them. _, @status = ::Process.wait2(@pid) end end diff --git a/releases.md b/releases.md index 0c44974..e565276 100644 --- a/releases.md +++ b/releases.md @@ -1,5 +1,9 @@ # Releases +## Unreleased + + - Use a process group for forked children, so that signals sent from the terminal are isolated to the foreground process. + ## v0.30.0 - `SIGTERM` is now graceful, the same as `SIGINT`, for better compatibility with Kubernetes and systemd.