mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
fix(bash): kill entire process group on cancel
Sets Setpgid on bash commands and sends SIGTERM/SIGKILL to the negative pgid so backgrounded children are cleaned up on esc. Also splits the status bar onto multiple lines on narrow terminals when a spinner is active.
This commit is contained in:
parent
fcd6d7c9a2
commit
b6529cf5c4
4 changed files with 70 additions and 12 deletions
|
|
@ -13,7 +13,6 @@ import (
|
|||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/patriceckhart/zot/internal/core"
|
||||
|
|
@ -69,6 +68,7 @@ func (t *BashTool) Execute(ctx context.Context, raw json.RawMessage, progress fu
|
|||
cmd := newShellCmd(runCtx, a.Command)
|
||||
cmd.Dir = cwd
|
||||
cmd.Env = os.Environ()
|
||||
setProcessGroup(cmd)
|
||||
|
||||
// Capture merged stdout+stderr with line-by-line streaming.
|
||||
pr, pw := io.Pipe()
|
||||
|
|
@ -225,14 +225,3 @@ func newShellCmd(ctx context.Context, command string) *exec.Cmd {
|
|||
}
|
||||
return exec.CommandContext(ctx, "/bin/sh", "-c", command)
|
||||
}
|
||||
|
||||
// killProcessGroup best-effort SIGTERM then SIGKILL.
|
||||
func killProcessGroup(cmd *exec.Cmd) {
|
||||
if cmd.Process == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.Process.Signal(syscall.SIGTERM)
|
||||
time.AfterFunc(3*time.Second, func() {
|
||||
_ = cmd.Process.Kill()
|
||||
})
|
||||
}
|
||||
|
|
|
|||
29
internal/agent/tools/bash_unix.go
Normal file
29
internal/agent/tools/bash_unix.go
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//go:build !windows
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// setProcessGroup puts the command in its own process group so
|
||||
// killProcessGroup can target the entire tree including background
|
||||
// children spawned with &.
|
||||
func setProcessGroup(cmd *exec.Cmd) {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
}
|
||||
|
||||
// killProcessGroup sends SIGTERM then SIGKILL to the entire process
|
||||
// group so backgrounded children (cmd &) are also cleaned up.
|
||||
func killProcessGroup(cmd *exec.Cmd) {
|
||||
if cmd.Process == nil {
|
||||
return
|
||||
}
|
||||
pgid := cmd.Process.Pid
|
||||
_ = syscall.Kill(-pgid, syscall.SIGTERM)
|
||||
time.AfterFunc(3*time.Second, func() {
|
||||
_ = syscall.Kill(-pgid, syscall.SIGKILL)
|
||||
})
|
||||
}
|
||||
20
internal/agent/tools/bash_windows.go
Normal file
20
internal/agent/tools/bash_windows.go
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//go:build windows
|
||||
|
||||
package tools
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func setProcessGroup(_ *exec.Cmd) {}
|
||||
|
||||
func killProcessGroup(cmd *exec.Cmd) {
|
||||
if cmd.Process == nil {
|
||||
return
|
||||
}
|
||||
_ = cmd.Process.Kill()
|
||||
time.AfterFunc(3*time.Second, func() {
|
||||
_ = cmd.Process.Kill()
|
||||
})
|
||||
}
|
||||
|
|
@ -1420,6 +1420,26 @@ func StatusBar(p StatusBarParams) []string {
|
|||
}
|
||||
|
||||
primary := leftBuilder.String()
|
||||
|
||||
// On narrow terminals the single line wraps badly. If the visible
|
||||
// width exceeds cols and we have a busy prefix, split: keep the
|
||||
// busy prefix on line 1, put model+stats on line 2.
|
||||
if p.Cols > 0 && p.BusyPrefix != "" && visibleWidth(primary) > p.Cols {
|
||||
busyLine := pad + p.BusyPrefix
|
||||
var infoBuilder strings.Builder
|
||||
infoBuilder.WriteString(pad)
|
||||
infoBuilder.WriteString(th.FG256(th.Muted, left))
|
||||
if middle != "" {
|
||||
infoBuilder.WriteString(pad)
|
||||
infoBuilder.WriteString(th.FG256(th.Muted, middle))
|
||||
}
|
||||
lines := []string{busyLine, infoBuilder.String()}
|
||||
if cwd != "" {
|
||||
lines = append(lines, pad+th.FG256(th.Muted, cwd))
|
||||
}
|
||||
return lines
|
||||
}
|
||||
|
||||
if cwd == "" {
|
||||
return []string{primary}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue