From c3b9374ae1ce7f1226c375161c5b606b83652bbc Mon Sep 17 00:00:00 2001 From: Snider Date: Sun, 15 Mar 2026 15:55:20 +0000 Subject: [PATCH] feat: add Detach option for process group isolation RunOptions.Detach creates the process in its own process group (Setpgid) and uses context.Background() instead of the parent context. Detached processes survive parent death. Co-Authored-By: Virgil --- service.go | 13 ++++++++++++- types.go | 4 ++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/service.go b/service.go index 44ccfc6..661dac9 100644 --- a/service.go +++ b/service.go @@ -9,6 +9,7 @@ import ( "os/exec" "sync" "sync/atomic" + "syscall" "time" "forge.lthn.ai/core/go/pkg/core" @@ -96,7 +97,12 @@ func (s *Service) Start(ctx context.Context, command string, args ...string) (*P func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Process, error) { id := fmt.Sprintf("proc-%d", s.idCounter.Add(1)) - procCtx, cancel := context.WithCancel(ctx) + // Detached processes use Background context so they survive parent death + parentCtx := ctx + if opts.Detach { + parentCtx = context.Background() + } + procCtx, cancel := context.WithCancel(parentCtx) cmd := exec.CommandContext(procCtx, opts.Command, opts.Args...) if opts.Dir != "" { @@ -106,6 +112,11 @@ func (s *Service) StartWithOptions(ctx context.Context, opts RunOptions) (*Proce cmd.Env = append(cmd.Environ(), opts.Env...) } + // Detached processes get their own process group + if opts.Detach { + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + } + // Set up pipes stdout, err := cmd.StdoutPipe() if err != nil { diff --git a/types.go b/types.go index 4489af7..8589122 100644 --- a/types.go +++ b/types.go @@ -73,6 +73,10 @@ type RunOptions struct { // DisableCapture disables output buffering. // By default, output is captured to a ring buffer. DisableCapture bool + // Detach creates the process in its own process group (Setpgid). + // Detached processes survive parent death and context cancellation. + // The context is replaced with context.Background() when Detach is true. + Detach bool } // Info provides a snapshot of process state without internal fields.