summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-05-13 14:31:49 +0300
committerPaul Buetow <paul@buetow.org>2026-05-13 14:31:49 +0300
commit42645a4889c1e45ad2ab85e0a371ef8e1054062e (patch)
tree314679bdfcc81229769dd165f3440e20e1ccc1d0
parent21ed685fb7e64c4dc36250c67832d594fc119307 (diff)
fix(pidpicker): eliminate defer-inside-goroutine-in-loop anti-patterns
Extract the per-pid goroutine body from scanAllThreadsFrom into a named scanThreadsWorker function. This removes both defer statements from the anonymous closure that was spawned inside a for loop: the semaphore release is now an explicit <-sem call immediately after I/O completes, and wg.Done() is called directly after scanThreadsWorker returns. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
-rw-r--r--internal/tui/pidpicker/proclist.go31
1 files changed, 20 insertions, 11 deletions
diff --git a/internal/tui/pidpicker/proclist.go b/internal/tui/pidpicker/proclist.go
index 73ff209..6f72505 100644
--- a/internal/tui/pidpicker/proclist.go
+++ b/internal/tui/pidpicker/proclist.go
@@ -180,6 +180,21 @@ func readThreadInfo(taskRoot string, entry fs.DirEntry, cmdline string) (Process
}, true
}
+// scanThreadsWorker fetches threads for one pid and appends results under mu.
+// It acquires the semaphore slot before doing I/O and releases it explicitly
+// on return, avoiding defer-inside-goroutine-in-loop anti-patterns.
+func scanThreadsWorker(procRoot string, pid int, sem chan struct{}, mu *sync.Mutex, threads *[]ProcessInfo) {
+ sem <- struct{}{} // acquire semaphore slot to cap concurrency
+ perProc, err := scanThreadsFrom(procRoot, pid)
+ <-sem // release semaphore slot regardless of outcome
+ if err != nil {
+ return
+ }
+ mu.Lock()
+ *threads = append(*threads, perProc...)
+ mu.Unlock()
+}
+
func scanAllThreadsFrom(procRoot string) ([]ProcessInfo, error) {
processes, err := scanProcessesFrom(procRoot)
if err != nil {
@@ -189,23 +204,17 @@ func scanAllThreadsFrom(procRoot string) ([]ProcessInfo, error) {
threads := make([]ProcessInfo, 0, len(processes)*8)
var mu sync.Mutex
var wg sync.WaitGroup
+ // sem caps the number of goroutines doing /proc I/O simultaneously.
sem := make(chan struct{}, 16)
for _, p := range processes {
pid := p.Pid
wg.Add(1)
+ // Each goroutine calls a named helper so no defer is registered inside
+ // an anonymous closure that is itself inside a for loop.
go func() {
- defer wg.Done()
- sem <- struct{}{}
- defer func() { <-sem }()
-
- perProc, err := scanThreadsFrom(procRoot, pid)
- if err != nil {
- return
- }
- mu.Lock()
- threads = append(threads, perProc...)
- mu.Unlock()
+ scanThreadsWorker(procRoot, pid, sem, &mu, &threads)
+ wg.Done()
}()
}
wg.Wait()