feat: add process manager and execution store types (0007b, 0007c)
Process spawn/wait/kill functions for subprocess management with output capture, timeout, and process group cleanup. DagRun and DagStepResult types for SQLite execution persistence. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package infra
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ProcessSpawn launches a subprocess using the given shell.
|
||||
// If shell is empty, "sh" is used. If command contains newlines it is treated
|
||||
// as a multi-line script: the content is written to a temp file and executed
|
||||
// with `shell <tempfile>`. Otherwise it is executed with `shell -c <command>`.
|
||||
// dir sets the working directory (empty = inherit). env sets the environment
|
||||
// (nil = inherit parent env). The process group is created with Setpgid so
|
||||
// that ProcessKill can target the whole group.
|
||||
func ProcessSpawn(command string, dir string, env []string, shell string) (*ProcessHandle, error) {
|
||||
if shell == "" {
|
||||
shell = "sh"
|
||||
}
|
||||
|
||||
var cmd *exec.Cmd
|
||||
|
||||
if strings.Contains(command, "\n") {
|
||||
// Multi-line script: write to a temp file and execute it.
|
||||
tmp, err := os.CreateTemp("", "fn-proc-*.sh")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("process_spawn: create temp file: %w", err)
|
||||
}
|
||||
if _, err := tmp.WriteString(command); err != nil {
|
||||
_ = os.Remove(tmp.Name())
|
||||
return nil, fmt.Errorf("process_spawn: write temp file: %w", err)
|
||||
}
|
||||
if err := tmp.Close(); err != nil {
|
||||
_ = os.Remove(tmp.Name())
|
||||
return nil, fmt.Errorf("process_spawn: close temp file: %w", err)
|
||||
}
|
||||
cmd = exec.Command(shell, tmp.Name())
|
||||
} else {
|
||||
cmd = exec.Command(shell, "-c", command)
|
||||
}
|
||||
|
||||
if dir != "" {
|
||||
cmd.Dir = dir
|
||||
}
|
||||
if len(env) > 0 {
|
||||
cmd.Env = env
|
||||
}
|
||||
|
||||
// New process group so we can kill all children as a group.
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
|
||||
// Use buffers instead of pipes to avoid race between Wait() and ReadAll().
|
||||
var stdoutBuf, stderrBuf bytes.Buffer
|
||||
cmd.Stdout = &stdoutBuf
|
||||
cmd.Stderr = &stderrBuf
|
||||
|
||||
start := time.Now()
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, fmt.Errorf("process_spawn: start: %w", err)
|
||||
}
|
||||
|
||||
return &ProcessHandle{
|
||||
Cmd: cmd,
|
||||
Pid: cmd.Process.Pid,
|
||||
StartTime: start,
|
||||
Dir: dir,
|
||||
stdout: &stdoutBuf,
|
||||
stderr: &stderrBuf,
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user