diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-13 14:31:49 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-13 14:31:49 +0300 |
| commit | 42645a4889c1e45ad2ab85e0a371ef8e1054062e (patch) | |
| tree | 314679bdfcc81229769dd165f3440e20e1ccc1d0 | |
| parent | 21ed685fb7e64c4dc36250c67832d594fc119307 (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.go | 31 |
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() |
