mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-19 08:02:22 +02:00
feat: manage task process lifetimes and preserve turn history
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
//go:build windows
|
||||
|
||||
package processguard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func configurePlatform(o *Options) error {
|
||||
if o.CgroupRoot != "" {
|
||||
return fmt.Errorf("cgroups are Linux-only")
|
||||
}
|
||||
if o.Mode == "process_group" {
|
||||
return fmt.Errorf("Windows tasks require Job Object containment")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func configureGuardian(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
|
||||
}
|
||||
func gatedChildMain(args []string) error {
|
||||
return fmt.Errorf("Unix launch gates are unavailable on Windows")
|
||||
}
|
||||
|
||||
type jobGroup struct {
|
||||
mu sync.Mutex
|
||||
job windows.Handle
|
||||
parent windows.Handle
|
||||
watcher *watchdog
|
||||
closed bool
|
||||
broken bool
|
||||
}
|
||||
|
||||
func newPlatformGroup(id string, o Options) (Group, error) {
|
||||
if err := configurePlatform(&o); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := "Local\\CyberStrikeAI-" + id
|
||||
g := &jobGroup{}
|
||||
w, err := startWatchdog(watchRequest{Name: name, Options: o}, func() {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
g.broken = true
|
||||
if !g.closed && g.job != 0 {
|
||||
_ = windows.TerminateJobObject(g.job, 1)
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fail := func(err error) (Group, error) { w.close(); return nil, err }
|
||||
namePtr, err := windows.UTF16PtrFromString(name)
|
||||
if err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
proc := windows.NewLazySystemDLL("kernel32.dll").NewProc("OpenJobObjectW")
|
||||
h, _, callErr := proc.Call(0x0004|0x0008, 0, uintptr(unsafe.Pointer(namePtr)))
|
||||
if h == 0 {
|
||||
return fail(callErr)
|
||||
}
|
||||
parent, err := windows.OpenProcess(windows.PROCESS_CREATE_PROCESS|windows.PROCESS_DUP_HANDLE, false, uint32(w.cmd.Process.Pid))
|
||||
if err != nil {
|
||||
windows.CloseHandle(windows.Handle(h))
|
||||
return fail(err)
|
||||
}
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.broken {
|
||||
windows.CloseHandle(parent)
|
||||
windows.CloseHandle(windows.Handle(h))
|
||||
return fail(fmt.Errorf("job guardian exited during setup"))
|
||||
}
|
||||
g.job = windows.Handle(h)
|
||||
g.parent = parent
|
||||
g.watcher = w
|
||||
return g, nil
|
||||
}
|
||||
func (g *jobGroup) Name() string { return "windows_job" }
|
||||
func (g *jobGroup) Prepare(cmd *exec.Cmd) (*Launch, error) {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.closed || g.broken {
|
||||
return nil, fmt.Errorf("job is closed")
|
||||
}
|
||||
if _, err := g.watcher.send(watchRequest{Op: "ping"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
// Windows inherits the job at CreateProcess time from this parent. The
|
||||
// guardian joined the job BEFORE acknowledging readiness, closing the
|
||||
// Start-then-Assign race and its suspended-process crash window.
|
||||
cmd.SysProcAttr.ParentProcess = syscall.Handle(g.parent)
|
||||
return &Launch{Commit: func() error { return nil }, Dispose: func() {}}, nil
|
||||
}
|
||||
func (g *jobGroup) Release(pid int) error { return nil }
|
||||
func (g *jobGroup) Close(ctx context.Context) error {
|
||||
g.mu.Lock()
|
||||
defer g.mu.Unlock()
|
||||
if g.closed {
|
||||
return nil
|
||||
}
|
||||
if err := windows.TerminateJobObject(g.job, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
type accounting struct {
|
||||
TotalUser, TotalKernel, PeriodUser, PeriodKernel int64
|
||||
PageFaults, TotalProcesses, ActiveProcesses, Terminated uint32
|
||||
}
|
||||
for {
|
||||
var info accounting
|
||||
if err := windows.QueryInformationJobObject(g.job, windows.JobObjectBasicAccountingInformation, uintptr(unsafe.Pointer(&info)), uint32(unsafe.Sizeof(info)), nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if info.ActiveProcesses == 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
g.closed = true
|
||||
return errors.Join(g.watcher.close(), windows.CloseHandle(g.parent), windows.CloseHandle(g.job))
|
||||
}
|
||||
func guardianMain(dec *json.Decoder, enc *json.Encoder) error {
|
||||
var req watchRequest
|
||||
if err := dec.Decode(&req); err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := windows.UTF16PtrFromString(req.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
job, err := windows.CreateJobObject(nil, name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer windows.CloseHandle(job)
|
||||
limits := windows.JOBOBJECT_EXTENDED_LIMIT_INFORMATION{}
|
||||
limits.BasicLimitInformation.LimitFlags = windows.JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | windows.JOB_OBJECT_LIMIT_ACTIVE_PROCESS | windows.JOB_OBJECT_LIMIT_JOB_MEMORY
|
||||
limits.BasicLimitInformation.ActiveProcessLimit = uint32(req.Options.MaxProcesses + 1)
|
||||
limits.JobMemoryLimit = uintptr(req.Options.MemoryMaxBytes)
|
||||
if _, err = windows.SetInformationJobObject(job, windows.JobObjectExtendedLimitInformation, uintptr(unsafe.Pointer(&limits)), uint32(unsafe.Sizeof(limits))); err != nil {
|
||||
return err
|
||||
}
|
||||
if req.Options.CPUQuotaMicros > 0 {
|
||||
rate := req.Options.CPUQuotaMicros / 10 / int64(runtime.NumCPU())
|
||||
if rate < 1 {
|
||||
rate = 1
|
||||
}
|
||||
if rate > 10000 {
|
||||
rate = 10000
|
||||
}
|
||||
cpu := struct{ Flags, Rate uint32 }{Flags: 1 | 4, Rate: uint32(rate)}
|
||||
if _, err = windows.SetInformationJobObject(job, windows.JobObjectCpuRateControlInformation, uintptr(unsafe.Pointer(&cpu)), uint32(unsafe.Sizeof(cpu))); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err = windows.AssignProcessToJobObject(job, windows.CurrentProcess()); err != nil {
|
||||
return err
|
||||
}
|
||||
return serveGuardian(dec, enc, func(req watchRequest) error {
|
||||
if req.Op != "ping" {
|
||||
return fmt.Errorf("unknown guardian command")
|
||||
}
|
||||
return nil
|
||||
}, func() error { return windows.TerminateJobObject(job, uint32(os.Getpid())) })
|
||||
}
|
||||
Reference in New Issue
Block a user