fix deadlock in CachedListModels: use dedicated modelsMu

content() holds sess.mu.RLock, then calls CachedListModels which
tried to acquire sess.mu.Lock for cache write — classic RLock/Lock
deadlock. Fix by giving the models cache its own mutex independent
of the session mu.
This commit is contained in:
Levi Neely 2026-07-18 12:54:38 +02:00
parent bb9b899e4e
commit 1db7fb9177
1 changed files with 10 additions and 7 deletions

View File

@ -35,6 +35,7 @@ type Session struct {
prevPrompt []byte // last submitted prompt; overwritten on each new submission
// Cached model list (expensive API call; refreshed every 24h).
modelsMu sync.Mutex
modelsCache string
modelsCacheAt time.Time
}
@ -49,30 +50,32 @@ const modelsCacheTTL = 24 * time.Hour
// CachedListModels returns the model list, using a 24h cache to avoid
// repeated expensive API calls.
// NOTE: Must NOT be called while holding sess.mu (caller content() holds RLock).
// Uses its own modelsMu to avoid deadlock.
func (sess *Session) CachedListModels() string {
sess.mu.RLock()
sess.modelsMu.Lock()
if sess.modelsCache != "" && time.Since(sess.modelsCacheAt) < modelsCacheTTL {
result := sess.modelsCache
sess.mu.RUnlock()
sess.modelsMu.Unlock()
return result
}
sess.mu.RUnlock()
sess.modelsMu.Unlock()
// Cache miss — fetch and store.
result := sess.Core.ListModels()
sess.mu.Lock()
sess.modelsMu.Lock()
sess.modelsCache = result
sess.modelsCacheAt = time.Now()
sess.mu.Unlock()
sess.modelsMu.Unlock()
return result
}
// InvalidateModelsCache clears the cached model list.
func (sess *Session) InvalidateModelsCache() {
sess.mu.Lock()
sess.modelsMu.Lock()
sess.modelsCache = ""
sess.modelsCacheAt = time.Time{}
sess.mu.Unlock()
sess.modelsMu.Unlock()
}
func (sess *Session) RunnableID() string { return sess.id }