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 <virgil@lethean.io>
This commit is contained in:
Snider 2026-03-15 15:55:20 +00:00
parent 4760cd8c80
commit c3b9374ae1
2 changed files with 16 additions and 1 deletions

View file

@ -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 {

View file

@ -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.