Escalate agent Close to SIGKILL so it terminates in one action
Agent 'Close' (agent-close) sent a single SIGTERM via Session.Kill and never escalated, so an agent that traps/ignores SIGTERM (e.g. opencode) stayed in the running tab bar until the user closed it again. Add Session.Terminate, which reuses terminateAndWait (SIGTERM, wait, then SIGKILL) but preserves the session entry so the exited pane stays readable, and route handleChildClose's agent path through it in a goroutine to keep the UI responsive during the stop timeout. Resolves the opencode double-close TODO item.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -101,6 +102,50 @@ func TestSpawnInstallsIdleDetectionBeforePublish(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTerminateEscalatesWithoutRemovingEntry(t *testing.T) {
|
||||
sess := NewSession(t.TempDir(), "test")
|
||||
c, err := sess.Spawn(SpawnSpec{
|
||||
Kind: KindAgent,
|
||||
Argv: []string{"sh", "-c", "trap '' TERM; echo ready; while :; do sleep 1; done"},
|
||||
}, 80, 24)
|
||||
if err != nil {
|
||||
t.Fatalf("spawn: %v", err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
if c.IsLive() {
|
||||
_ = c.signal(syscall.SIGKILL)
|
||||
}
|
||||
})
|
||||
waitUntilLive(t, c)
|
||||
waitForStreamText(t, c, "ready")
|
||||
|
||||
start := time.Now()
|
||||
if err := sess.Terminate(c.ID, syscall.SIGTERM); err != nil {
|
||||
t.Fatalf("Terminate: %v", err)
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed < childStopTimeout {
|
||||
t.Fatalf("Terminate returned before SIGKILL fallback: elapsed=%s timeout=%s", elapsed, childStopTimeout)
|
||||
}
|
||||
waitUntilNotLive(t, c)
|
||||
|
||||
if got := sess.FindChild(c.ID); got == nil {
|
||||
t.Fatalf("Terminate removed child entry %s", c.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func waitForStreamText(t *testing.T, c *Child, want string) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
for time.Now().Before(deadline) {
|
||||
b, _ := c.StreamRead(0)
|
||||
if strings.Contains(string(b), want) {
|
||||
return
|
||||
}
|
||||
time.Sleep(20 * time.Millisecond)
|
||||
}
|
||||
t.Fatalf("child %s never wrote %q", c.ID, want)
|
||||
}
|
||||
|
||||
func waitUntilLive(t *testing.T, c *Child) {
|
||||
t.Helper()
|
||||
deadline := time.Now().Add(5 * time.Second)
|
||||
|
||||
Reference in New Issue
Block a user