mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-23 01:40:50 +02:00
feat: manage task process lifetimes and preserve turn history
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
//go:build !windows
|
||||
|
||||
package processguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
type unixGroup struct {
|
||||
mu sync.Mutex
|
||||
watcher *watchdog
|
||||
pids map[int]struct{}
|
||||
closed bool
|
||||
}
|
||||
|
||||
func newUnixGroup() (*unixGroup, error) {
|
||||
g := &unixGroup{pids: make(map[int]struct{})}
|
||||
w, err := startWatchdog(watchRequest{Name: "process_group"}, func() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.closed = true
|
||||
for pid := range g.pids {
|
||||
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.watcher = w
|
||||
return g, nil
|
||||
}
|
||||
func (g *unixGroup) Name() string { return "process_group_watchdog" }
|
||||
func configureGuardian(cmd *exec.Cmd) { cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true} }
|
||||
|
||||
type childSpec struct {
|
||||
Path string
|
||||
Args []string
|
||||
}
|
||||
|
||||
func (g *unixGroup) Prepare(cmd *exec.Cmd) (*Launch, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.closed {
|
||||
return nil, fmt.Errorf("process group is closed")
|
||||
}
|
||||
// A dead guardian rejects subsequent launches before user code is executed.
|
||||
if _, err := g.watcher.send(watchRequest{Op: "ping"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
read, write, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec, _ := json.Marshal(childSpec{Path: cmd.Path, Args: cmd.Args})
|
||||
exe, err := os.Executable()
|
||||
if err != nil {
|
||||
read.Close()
|
||||
write.Close()
|
||||
return nil, err
|
||||
}
|
||||
fd := 3 + len(cmd.ExtraFiles)
|
||||
cmd.ExtraFiles = append(cmd.ExtraFiles, read)
|
||||
cmd.Path = exe
|
||||
cmd.Args = []string{exe, childArg, strconv.Itoa(fd), base64.RawStdEncoding.EncodeToString(spec)}
|
||||
return &Launch{Dispose: func() { read.Close(); write.Close() }, Commit: func() error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.closed {
|
||||
return fmt.Errorf("process group is closed")
|
||||
}
|
||||
pid := cmd.Process.Pid
|
||||
if _, err := g.watcher.send(watchRequest{Op: "add", PID: pid}); err != nil {
|
||||
return err
|
||||
}
|
||||
g.pids[pid] = struct{}{}
|
||||
_, err := write.Write([]byte{1})
|
||||
return err
|
||||
}}, nil
|
||||
}
|
||||
func (g *unixGroup) Release(pid int) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if _, ok := g.pids[pid]; !ok {
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-g.watcher.done:
|
||||
delete(g.pids, pid)
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
if _, err := g.watcher.send(watchRequest{Op: "release", PID: pid}); err != nil {
|
||||
return err
|
||||
}
|
||||
delete(g.pids, pid)
|
||||
return nil
|
||||
}
|
||||
func (g *unixGroup) Close(ctx context.Context) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.closed = true
|
||||
for pid := range g.pids {
|
||||
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||
}
|
||||
for {
|
||||
for pid := range g.pids {
|
||||
if syscall.Kill(-pid, 0) == syscall.ESRCH {
|
||||
delete(g.pids, pid)
|
||||
}
|
||||
}
|
||||
if len(g.pids) == 0 {
|
||||
return g.watcher.close()
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
func gatedChildMain(args []string) error {
|
||||
if len(args) != 2 {
|
||||
return fmt.Errorf("invalid internal launch")
|
||||
}
|
||||
fd, err := strconv.Atoi(args[0])
|
||||
if err != nil || fd < 3 {
|
||||
return fmt.Errorf("invalid launch gate")
|
||||
}
|
||||
gate := os.NewFile(uintptr(fd), "launch-gate")
|
||||
var token [1]byte
|
||||
if _, err = io.ReadFull(gate, token[:]); err != nil {
|
||||
return fmt.Errorf("owner exited before launch: %w", err)
|
||||
}
|
||||
gate.Close()
|
||||
if token[0] != 1 {
|
||||
return fmt.Errorf("invalid launch token")
|
||||
}
|
||||
b, err := base64.RawStdEncoding.DecodeString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var spec childSpec
|
||||
if err = json.Unmarshal(b, &spec); err != nil {
|
||||
return err
|
||||
}
|
||||
return syscall.Exec(spec.Path, spec.Args, os.Environ())
|
||||
}
|
||||
func groupGuardian(dec *json.Decoder, enc *json.Encoder) error {
|
||||
pids := make(map[int]struct{})
|
||||
return serveGuardian(dec, enc, func(req watchRequest) error {
|
||||
switch req.Op {
|
||||
case "ping":
|
||||
case "add":
|
||||
if req.PID <= 1 {
|
||||
return fmt.Errorf("invalid PID")
|
||||
}
|
||||
pids[req.PID] = struct{}{}
|
||||
case "release":
|
||||
delete(pids, req.PID)
|
||||
default:
|
||||
return fmt.Errorf("unknown guardian command")
|
||||
}
|
||||
return nil
|
||||
}, func() error {
|
||||
for pid := range pids {
|
||||
_ = syscall.Kill(-pid, syscall.SIGKILL)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user