Modified the initSandboxV2 function to return a per-request copy of the SandboxConfig, ensuring that concurrent requests to the same assistant have their own mutable configuration. Updated related logging and cleanup processes to utilize the new configuration structure. Additionally, introduced a DefaultOneShotMaxAge constant to manage the lifecycle of OneShot containers more effectively.
138 lines
3.8 KiB
Go
138 lines
3.8 KiB
Go
package sandbox
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/yaoapp/yao/monitor"
|
|
)
|
|
|
|
func init() {
|
|
monitor.Register(&sandboxWatcher{})
|
|
}
|
|
|
|
type sandboxWatcher struct{}
|
|
|
|
func (w *sandboxWatcher) Name() string { return "sandbox" }
|
|
func (w *sandboxWatcher) Interval() time.Duration { return 30 * time.Second }
|
|
|
|
func (w *sandboxWatcher) Check(ctx context.Context) []monitor.Alert {
|
|
if mgr == nil {
|
|
return nil
|
|
}
|
|
|
|
var alerts []monitor.Alert
|
|
mgr.boxes.Range(func(_, v any) bool {
|
|
b := v.(*Box)
|
|
|
|
status := b.inspectStatus(ctx)
|
|
old, _ := b.status.Swap(status).(string)
|
|
if old != "" && old != status {
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Info,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("status %s → %s", old, status),
|
|
})
|
|
if status == "running" && old != "running" {
|
|
b.touch()
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Trace,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("touch on resume, lastCall reset to %s",
|
|
time.UnixMilli(b.lastCall.Load()).Format(time.RFC3339)),
|
|
})
|
|
}
|
|
}
|
|
|
|
if status != "running" {
|
|
return true
|
|
}
|
|
|
|
// maxLifetime: independent of idle — prevents indefinitely running containers
|
|
if b.policy == LongRunning {
|
|
if lifetime := b.maxLifetime(); lifetime > 0 {
|
|
age := time.Since(b.createdAt)
|
|
if age > lifetime {
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Warn,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("lifetime expired (%s), removing", lifetime),
|
|
Action: func(ctx context.Context) { mgr.Remove(ctx, b.id) },
|
|
})
|
|
} else {
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Trace,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("lifetime remaining %s (max=%s)",
|
|
(lifetime - age).Round(time.Second), lifetime),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// OneShot safety net: these containers should have been removed by
|
|
// LifecycleAction right after execution. If they still exist after
|
|
// DefaultOneShotMaxAge it means cleanup failed (e.g. process crash,
|
|
// cfg.ID race before the fix). Remove them based on createdAt so we
|
|
// never kill a container that is still actively executing.
|
|
if b.policy == OneShot {
|
|
age := time.Since(b.createdAt)
|
|
if age > DefaultOneShotMaxAge {
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Warn,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("oneshot exceeded max age (%s > %s), removing", age.Round(time.Second), DefaultOneShotMaxAge),
|
|
Action: func(ctx context.Context) { mgr.Remove(ctx, b.id) },
|
|
})
|
|
} else {
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Trace,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("oneshot age %s (max=%s)",
|
|
age.Round(time.Second), DefaultOneShotMaxAge),
|
|
})
|
|
}
|
|
return true
|
|
}
|
|
|
|
idle := time.Since(b.idleSince())
|
|
timeout := b.idleTimeout()
|
|
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Trace,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("heartbeat status=%s policy=%s idle=%s timeout=%s",
|
|
status, b.policy, idle.Round(time.Second), timeout),
|
|
})
|
|
|
|
if timeout <= 0 || idle <= timeout {
|
|
return true
|
|
}
|
|
|
|
switch b.policy {
|
|
case Session:
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Warn,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("session idle expired (idle=%s, timeout=%s), removing", idle.Round(time.Second), timeout),
|
|
Action: func(ctx context.Context) {
|
|
mgr.Remove(ctx, b.id)
|
|
},
|
|
})
|
|
|
|
case LongRunning:
|
|
alerts = append(alerts, monitor.Alert{
|
|
Level: monitor.Warn,
|
|
Target: "box:" + b.id,
|
|
Message: fmt.Sprintf("longrunning idle expired (idle=%s, timeout=%s), stopping", idle.Round(time.Second), timeout),
|
|
Action: func(ctx context.Context) {
|
|
b.Stop(ctx)
|
|
},
|
|
})
|
|
}
|
|
|
|
return true
|
|
})
|
|
return alerts
|
|
}
|