fix unit run

This commit is contained in:
booleanmaybe 2026-04-05 12:37:25 -04:00
parent c466ca3059
commit 1cf10874a8
4 changed files with 42 additions and 0 deletions

18
service/cmdutil_unix.go Normal file
View file

@ -0,0 +1,18 @@
//go:build !windows
package service
import (
"os/exec"
"syscall"
)
// setProcessGroup configures the command to run in its own process group
// and overrides Cancel to kill the entire group (parent + children).
func setProcessGroup(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error {
// negative pid → kill the whole process group
return syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
}

View file

@ -0,0 +1,10 @@
//go:build windows
package service
import "os/exec"
// setProcessGroup is a no-op on Windows.
// Windows has no process-group kill equivalent to Unix's kill(-pgid).
// cmd.WaitDelay (set by the caller) bounds the pipe drain if children outlive the parent.
func setProcessGroup(_ *exec.Cmd) {}

View file

@ -192,6 +192,8 @@ func (te *TriggerEngine) execRun(ctx context.Context, entry triggerEntry, tc *ru
defer cancel()
cmd := exec.CommandContext(runCtx, "sh", "-c", cmdStr) //nolint:gosec // cmdStr is a user-configured trigger action, intentionally dynamic
setProcessGroup(cmd)
cmd.WaitDelay = 3 * time.Second
output, err := cmd.CombinedOutput()
if err != nil {
slog.Error("trigger run() command failed",

View file

@ -2,6 +2,7 @@ package service
import (
"context"
"runtime"
"strings"
"testing"
"time"
@ -374,8 +375,17 @@ func TestTriggerEngine_DepthExceededAtGateLevel(t *testing.T) {
}
// --- run() trigger ---
// These tests invoke sh -c which requires a Unix shell.
func skipOnWindows(t *testing.T) {
t.Helper()
if runtime.GOOS == "windows" {
t.Skip("run() triggers use sh -c, skipping on Windows")
}
}
func TestTriggerEngine_RunCommand(t *testing.T) {
skipOnWindows(t)
entry := parseTriggerEntry(t, "echo trigger",
`after update where new.status = "done" run("echo " + old.id)`)
@ -394,6 +404,7 @@ func TestTriggerEngine_RunCommand(t *testing.T) {
}
func TestTriggerEngine_RunCommandFailure(t *testing.T) {
skipOnWindows(t)
entry := parseTriggerEntry(t, "failing command",
`after update where new.status = "done" run("exit 1")`)
@ -412,6 +423,7 @@ func TestTriggerEngine_RunCommandFailure(t *testing.T) {
}
func TestTriggerEngine_RunCommandTimeout(t *testing.T) {
skipOnWindows(t)
// use a run() trigger whose command outlives the parent context's deadline
entry := parseTriggerEntry(t, "slow command",
`after update where new.status = "done" run("sleep 30")`)