Stacks: Duplicate, Labels and Network in the form, task verdict badges, a short Where; Cancel really cancels
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
a229f2b597
commit
1e992ca1eb
+37
-6
@@ -12,6 +12,7 @@ import (
|
||||
"os/exec"
|
||||
"sort"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -31,10 +32,11 @@ type Task struct {
|
||||
ExitCode int
|
||||
Err string
|
||||
|
||||
mu sync.Mutex
|
||||
buf bytes.Buffer
|
||||
subs map[chan []byte]struct{}
|
||||
cancel context.CancelFunc
|
||||
mu sync.Mutex
|
||||
buf bytes.Buffer
|
||||
subs map[chan []byte]struct{}
|
||||
cancel context.CancelFunc
|
||||
onCancel func()
|
||||
}
|
||||
|
||||
type Snapshot struct {
|
||||
@@ -71,6 +73,15 @@ func (m *Manager) Start(kind, title string, cmd *exec.Cmd) (*Task, error) {
|
||||
subs: map[chan []byte]struct{}{}, cancel: cancel}
|
||||
w := &writer{t: t}
|
||||
cmd.Stdout, cmd.Stderr = w, w
|
||||
// Its own process group: a task is usually `sh -c "a && b"`, and cancelling must reach a and
|
||||
// b, not just the shell — killing only the shell left compose running and, because compose
|
||||
// still held the output pipe, left the task "running" too. WaitDelay is the backstop for
|
||||
// anything that escapes the group and keeps the pipe open.
|
||||
if cmd.SysProcAttr == nil {
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
||||
}
|
||||
cmd.SysProcAttr.Setpgid = true
|
||||
cmd.WaitDelay = 5 * time.Second
|
||||
if err := cmd.Start(); err != nil {
|
||||
cancel()
|
||||
return nil, err
|
||||
@@ -87,8 +98,20 @@ func (m *Manager) Start(kind, title string, cmd *exec.Cmd) (*Task, error) {
|
||||
select {
|
||||
case err = <-done:
|
||||
case <-ctx.Done():
|
||||
_ = cmd.Process.Kill()
|
||||
err = <-done
|
||||
t.mu.Lock()
|
||||
hook := t.onCancel
|
||||
t.mu.Unlock()
|
||||
if hook != nil {
|
||||
go hook()
|
||||
}
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGTERM) // asked first: compose and apt both stop cleanly
|
||||
select {
|
||||
case err = <-done:
|
||||
case <-time.After(4 * time.Second):
|
||||
_ = syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
|
||||
_ = cmd.Process.Kill()
|
||||
err = <-done
|
||||
}
|
||||
t.mu.Lock()
|
||||
t.Err = "cancelled"
|
||||
t.mu.Unlock()
|
||||
@@ -207,6 +230,14 @@ func (t *Task) Subscribe() (backlog []byte, ch chan []byte, unsub func()) {
|
||||
|
||||
func (t *Task) Cancel() { t.cancel() }
|
||||
|
||||
// OnCancel sets something to do, besides stopping the task's own processes, when the task is
|
||||
// cancelled: whatever it started that isn't its child (a command inside a container).
|
||||
func (t *Task) OnCancel(fn func()) {
|
||||
t.mu.Lock()
|
||||
t.onCancel = fn
|
||||
t.mu.Unlock()
|
||||
}
|
||||
|
||||
type writer struct{ t *Task }
|
||||
|
||||
func (w *writer) Write(p []byte) (int, error) {
|
||||
|
||||
Reference in New Issue
Block a user