diff --git a/src/terminal.go b/src/terminal.go index 16c187b8..bca98530 100644 --- a/src/terminal.go +++ b/src/terminal.go @@ -9,6 +9,7 @@ import ( "math" "net" "os" + "os/exec" "os/signal" "regexp" "sort" @@ -377,6 +378,7 @@ type Terminal struct { version int64 revision revision bgVersion int64 + runningCmds *util.ConcurrentSet[*exec.Cmd] reqBox *util.EventBox initialPreviewOpts previewOpts previewOpts previewOpts @@ -1030,6 +1032,7 @@ func NewTerminal(opts *Options, eventBox *util.EventBox, executor *util.Executor proxyScript: opts.ProxyScript, merger: EmptyMerger(revision{}), selected: make(map[int32]selectedItem), + runningCmds: util.NewConcurrentSet[*exec.Cmd](), reqBox: util.NewEventBox(), initialPreviewOpts: opts.Preview, previewOpts: opts.Preview, @@ -4377,6 +4380,7 @@ func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(stri reader := bufio.NewReader(out) var output string if err := cmd.Start(); err == nil { + t.runningCmds.Add(cmd) if firstLineOnly { output, _ = reader.ReadString('\n') output = strings.TrimRight(output, "\r\n") @@ -4385,6 +4389,7 @@ func (t *Terminal) captureAsync(a action, firstLineOnly bool, callback func(stri output = string(bytes) } cmd.Wait() + t.runningCmds.Remove(cmd) } t.callbackChan <- versionedCallback{version, func() { callback(output) }} } @@ -5053,6 +5058,9 @@ func (t *Terminal) Loop() error { if code <= ExitNoMatch && t.history != nil { t.history.append(string(t.input)) } + t.runningCmds.ForEach(func(cmd *exec.Cmd) { + util.KillCommand(cmd) + }) running = false t.mutex.Unlock() } diff --git a/src/util/concurrent_set.go b/src/util/concurrent_set.go new file mode 100644 index 00000000..c2ffc619 --- /dev/null +++ b/src/util/concurrent_set.go @@ -0,0 +1,39 @@ +package util + +import "sync" + +// ConcurrentSet is a thread-safe set implementation. +type ConcurrentSet[T comparable] struct { + lock sync.RWMutex + items map[T]struct{} +} + +// NewConcurrentSet creates a new ConcurrentSet. +func NewConcurrentSet[T comparable]() *ConcurrentSet[T] { + return &ConcurrentSet[T]{ + items: make(map[T]struct{}), + } +} + +// Add adds an item to the set. +func (s *ConcurrentSet[T]) Add(item T) { + s.lock.Lock() + defer s.lock.Unlock() + s.items[item] = struct{}{} +} + +// Remove removes an item from the set. +func (s *ConcurrentSet[T]) Remove(item T) { + s.lock.Lock() + defer s.lock.Unlock() + delete(s.items, item) +} + +// ForEach iterates over each item in the set and applies the provided function. +func (s *ConcurrentSet[T]) ForEach(fn func(item T)) { + s.lock.RLock() + defer s.lock.RUnlock() + for item := range s.items { + fn(item) + } +}