11package pubgrub
22
3- import "fmt"
3+ import (
4+ "fmt"
5+ "sync"
6+ )
47
58// CachedSource wraps a Source and caches GetVersions and GetDependencies calls
69// to improve performance when the same queries are made repeatedly.
@@ -23,6 +26,8 @@ import "fmt"
2326type CachedSource struct {
2427 source Source
2528
29+ mu sync.Mutex
30+
2631 // Cache for GetVersions results
2732 versionsCache map [Name ][]Version
2833 versionsCalls int
@@ -45,27 +50,37 @@ func NewCachedSource(source Source) *CachedSource {
4550
4651// GetVersions returns all available versions for a package, caching the result.
4752func (c * CachedSource ) GetVersions (name Name ) ([]Version , error ) {
53+ c .mu .Lock ()
4854 c .versionsCalls ++
4955
5056 // Check cache first
5157 if versions , ok := c .versionsCache [name ]; ok {
5258 c .versionsCacheHits ++
53- return versions , nil
59+ out := cloneVersions (versions )
60+ c .mu .Unlock ()
61+ return out , nil
5462 }
63+ c .mu .Unlock ()
5564
5665 // Cache miss - fetch from underlying source
5766 versions , err := c .source .GetVersions (name )
5867 if err != nil {
5968 return nil , err
6069 }
6170
71+ cloned := cloneVersions (versions )
72+
6273 // Store in cache
63- c .versionsCache [name ] = versions
64- return versions , nil
74+ c .mu .Lock ()
75+ c .versionsCache [name ] = cloned
76+ c .mu .Unlock ()
77+
78+ return cloneVersions (cloned ), nil
6579}
6680
6781// GetDependencies returns dependencies for a specific package version, caching the result.
6882func (c * CachedSource ) GetDependencies (name Name , version Version ) ([]Term , error ) {
83+ c .mu .Lock ()
6984 c .depsCalls ++
7085
7186 // Create cache key from name and version
@@ -74,18 +89,26 @@ func (c *CachedSource) GetDependencies(name Name, version Version) ([]Term, erro
7489 // Check cache first
7590 if deps , ok := c .depsCache [key ]; ok {
7691 c .depsCacheHits ++
77- return deps , nil
92+ out := cloneTerms (deps )
93+ c .mu .Unlock ()
94+ return out , nil
7895 }
96+ c .mu .Unlock ()
7997
8098 // Cache miss - fetch from underlying source
8199 deps , err := c .source .GetDependencies (name , version )
82100 if err != nil {
83101 return nil , err
84102 }
85103
104+ cloned := cloneTerms (deps )
105+
86106 // Store in cache
87- c .depsCache [key ] = deps
88- return deps , nil
107+ c .mu .Lock ()
108+ c .depsCache [key ] = cloned
109+ c .mu .Unlock ()
110+
111+ return cloneTerms (cloned ), nil
89112}
90113
91114// CacheStats returns statistics about cache performance.
@@ -105,6 +128,7 @@ type CacheStats struct {
105128
106129// GetCacheStats returns cache performance statistics.
107130func (c * CachedSource ) GetCacheStats () CacheStats {
131+ c .mu .Lock ()
108132 stats := CacheStats {
109133 VersionsCalls : c .versionsCalls ,
110134 VersionsCacheHits : c .versionsCacheHits ,
@@ -113,6 +137,7 @@ func (c *CachedSource) GetCacheStats() CacheStats {
113137 TotalCalls : c .versionsCalls + c .depsCalls ,
114138 TotalCacheHits : c .versionsCacheHits + c .depsCacheHits ,
115139 }
140+ c .mu .Unlock ()
116141
117142 if stats .VersionsCalls > 0 {
118143 stats .VersionsHitRate = float64 (stats .VersionsCacheHits ) / float64 (stats .VersionsCalls )
@@ -131,10 +156,30 @@ func (c *CachedSource) GetCacheStats() CacheStats {
131156
132157// ClearCache clears all cached data while preserving the underlying source.
133158func (c * CachedSource ) ClearCache () {
159+ c .mu .Lock ()
134160 c .versionsCache = make (map [Name ][]Version )
135161 c .depsCache = make (map [string ][]Term )
136162 c .versionsCalls = 0
137163 c .versionsCacheHits = 0
138164 c .depsCalls = 0
139165 c .depsCacheHits = 0
166+ c .mu .Unlock ()
167+ }
168+
169+ func cloneVersions (in []Version ) []Version {
170+ if len (in ) == 0 {
171+ return nil
172+ }
173+ out := make ([]Version , len (in ))
174+ copy (out , in )
175+ return out
176+ }
177+
178+ func cloneTerms (in []Term ) []Term {
179+ if len (in ) == 0 {
180+ return nil
181+ }
182+ out := make ([]Term , len (in ))
183+ copy (out , in )
184+ return out
140185}
0 commit comments