From e58f376e4c4e6d31fd9158582222dfff1c90b3df Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 3 Apr 2026 23:53:19 +0000 Subject: [PATCH] feat(process): signal process groups --- process.go | 8 ++++++++ process_test.go | 22 ++++++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/process.go b/process.go index 9a2ad0b..80f43b0 100644 --- a/process.go +++ b/process.go @@ -188,6 +188,14 @@ func (p *Process) Signal(sig os.Signal) error { return nil } + if p.killGroup { + sysSig, ok := sig.(syscall.Signal) + if !ok { + return p.cmd.Process.Signal(sig) + } + return syscall.Kill(-p.cmd.Process.Pid, sysSig) + } + return p.cmd.Process.Signal(sig) } diff --git a/process_test.go b/process_test.go index 302bc9e..0dd9ebe 100644 --- a/process_test.go +++ b/process_test.go @@ -225,6 +225,28 @@ func TestProcess_Signal(t *testing.T) { err = proc.Signal(os.Interrupt) assert.ErrorIs(t, err, ErrProcessNotRunning) }) + + t.Run("signals process group when kill group is enabled", func(t *testing.T) { + svc, _ := newTestService(t) + + proc, err := svc.StartWithOptions(context.Background(), RunOptions{ + Command: "sh", + Args: []string{"-c", "trap '' INT; sh -c 'trap - INT; sleep 60' & wait"}, + Detach: true, + KillGroup: true, + }) + require.NoError(t, err) + + err = proc.Signal(os.Interrupt) + assert.NoError(t, err) + + select { + case <-proc.Done(): + // Good - the whole process group responded to the signal. + case <-time.After(5 * time.Second): + t.Fatal("process group should have been terminated by signal") + } + }) } func TestProcess_CloseStdin(t *testing.T) {