fix(tools): use sync.Once for thread-safe Stop() in SessionManager

The Stop() method previously used a select/default pattern which was not
safe under concurrent calls — two goroutines could both pass the check
and attempt to close the same channel, causing a panic.

Replace with sync.Once to guarantee exactly-once close semantics,
matching the documented contract of being safe for concurrent use.

Review feedback: afjcjsbx
This commit is contained in:
程智超0668000959 2026-06-02 19:45:31 +08:00
parent bb57e0498c
commit e70a9fca7c
2 changed files with 7 additions and 9 deletions

View file

@ -172,6 +172,7 @@ type SessionManager struct {
mu sync.RWMutex mu sync.RWMutex
sessions map[string]*ProcessSession sessions map[string]*ProcessSession
stopCh chan struct{} stopCh chan struct{}
stopOnce sync.Once
} }
func NewSessionManager() *SessionManager { func NewSessionManager() *SessionManager {
@ -197,16 +198,13 @@ func NewSessionManager() *SessionManager {
return sm return sm
} }
// Stop shuts down the background cleanup goroutine. Safe to call multiple times. // Stop shuts down the background cleanup goroutine. Safe to call multiple
// After Stop returns, the SessionManager is still usable — only the cleanup // times from concurrent goroutines. After Stop returns, the SessionManager
// goroutine is terminated. // is still usable — only the cleanup goroutine is terminated.
func (sm *SessionManager) Stop() { func (sm *SessionManager) Stop() {
select { sm.stopOnce.Do(func() {
case <-sm.stopCh:
// already closed
default:
close(sm.stopCh) close(sm.stopCh)
} })
} }
// cleanupOldSessions removes sessions that are done and older than 30 minutes // cleanupOldSessions removes sessions that are done and older than 30 minutes

View file

@ -1,4 +1,4 @@
package tools package tools
import ( import (
"context" "context"