diff --git a/session/session.go b/session/session.go index f9bc1dc..e6f6a99 100644 --- a/session/session.go +++ b/session/session.go @@ -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 }