@@ -59,27 +59,35 @@ func NewCacheManager() (*CacheManager, error) {
5959
6060// Get retrieves a cached commit message if it exists.
6161func (cm * CacheManager ) Get (provider types.LLMProvider , diff string , opts * types.GenerationOptions ) (* types.CacheEntry , bool ) {
62- cm .mutex .RLock ()
63- defer cm .mutex .RUnlock ()
64-
6562 key := cm .hasher .GenerateCacheKey (provider , diff , opts )
66- entry , exists := cm .entries [key ]
6763
64+ // Phase 1: Read with RLock to check existence and copy the entry
65+ cm .mutex .RLock ()
66+ entry , exists := cm .entries [key ]
6867 if ! exists {
68+ cm .mutex .RUnlock ()
69+ // Update miss statistics with write lock
70+ cm .mutex .Lock ()
6971 cm .stats .TotalMisses ++
7072 cm .updateHitRate ()
73+ cm .mutex .Unlock ()
7174 return nil , false
7275 }
7376
74- // Update access statistics
77+ // Create a copy of the entry to avoid external mutation
78+ entryCopy := * entry
79+ cm .mutex .RUnlock ()
80+
81+ // Phase 2: Update shared stats and entry with write lock
82+ cm .mutex .Lock ()
83+ // Update access statistics on the original entry
7584 entry .LastAccessedAt = time .Now ().Format (time .RFC3339 )
7685 entry .AccessCount ++
7786 cm .stats .TotalHits ++
78-
79- // Update hit rate
8087 cm .updateHitRate ()
88+ cm .mutex .Unlock ()
8189
82- return entry , true
90+ return & entryCopy , true
8391}
8492
8593// Set stores a commit message in the cache.
@@ -133,10 +141,10 @@ func (cm *CacheManager) Clear() error {
133141
134142// GetStats returns cache statistics.
135143func (cm * CacheManager ) GetStats () * types.CacheStats {
136- cm .mutex .RLock ()
137- defer cm .mutex .RUnlock ()
144+ cm .mutex .Lock ()
145+ defer cm .mutex .Unlock ()
138146
139- // Calculate additional stats
147+ // Calculate additional stats (mutates cm.stats)
140148 cm .calculateStats ()
141149
142150 // Return a copy to avoid race conditions
@@ -149,7 +157,17 @@ func (cm *CacheManager) Cleanup() error {
149157 cm .mutex .Lock ()
150158 defer cm .mutex .Unlock ()
151159
152- return cm .cleanupOldEntries ()
160+ // Clean up old entries in memory
161+ if err := cm .cleanupOldEntries (); err != nil {
162+ return fmt .Errorf ("failed to cleanup old entries: %w" , err )
163+ }
164+
165+ // Persist the cleaned cache to disk
166+ if err := cm .saveCache (); err != nil {
167+ return fmt .Errorf ("failed to persist cache after cleanup: %w" , err )
168+ }
169+
170+ return nil
153171}
154172
155173// loadCache loads the cache from disk.
@@ -176,14 +194,21 @@ func (cm *CacheManager) loadCache() error {
176194 if cacheData .Stats != nil {
177195 cm .stats = cacheData .Stats
178196 }
197+ if cm .entries == nil {
198+ cm .entries = make (map [string ]* types.CacheEntry )
199+ }
200+ if cm .stats == nil {
201+ cm .stats = & types.CacheStats {}
202+ }
203+ cm .stats .TotalEntries = len (cm .entries )
179204
180205 return nil
181206}
182207
183208// saveCache saves the cache to disk.
184209func (cm * CacheManager ) saveCache () error {
185210 // Ensure directory exists
186- if err := os .MkdirAll (filepath .Dir (cm .filePath ), 0755 ); err != nil {
211+ if err := os .MkdirAll (filepath .Dir (cm .filePath ), 0700 ); err != nil {
187212 return fmt .Errorf ("failed to create cache directory: %w" , err )
188213 }
189214
@@ -202,7 +227,7 @@ func (cm *CacheManager) saveCache() error {
202227 return fmt .Errorf ("failed to marshal cache data: %w" , err )
203228 }
204229
205- if err := os .WriteFile (cm .filePath , data , 0644 ); err != nil {
230+ if err := os .WriteFile (cm .filePath , data , 0600 ); err != nil {
206231 return fmt .Errorf ("failed to write cache file: %w" , err )
207232 }
208233
@@ -233,7 +258,7 @@ func (cm *CacheManager) cleanupOldEntries() error {
233258
234259 // If we still have too many entries, remove least recently accessed
235260 if len (cm .entries )- len (keysToDelete ) > cm .config .MaxEntries {
236- cm .removeLeastAccessed (keysToDelete )
261+ keysToDelete = cm .removeLeastAccessed (keysToDelete )
237262 }
238263
239264 // Delete the selected entries
@@ -247,7 +272,7 @@ func (cm *CacheManager) cleanupOldEntries() error {
247272}
248273
249274// removeLeastAccessed removes the least recently accessed entries.
250- func (cm * CacheManager ) removeLeastAccessed (existingKeysToDelete []string ) {
275+ func (cm * CacheManager ) removeLeastAccessed (existingKeysToDelete []string ) [] string {
251276 type entryWithKey struct {
252277 key string
253278 entry * types.CacheEntry
@@ -282,6 +307,8 @@ func (cm *CacheManager) removeLeastAccessed(existingKeysToDelete []string) {
282307 for i := 0 ; i < entriesToRemove && i < len (entries ); i ++ {
283308 existingKeysToDelete = append (existingKeysToDelete , entries [i ].key )
284309 }
310+
311+ return existingKeysToDelete
285312}
286313
287314// updateHitRate calculates and updates the hit rate.
0 commit comments