-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathcached_source.go
More file actions
185 lines (155 loc) · 4.39 KB
/
Copy pathcached_source.go
File metadata and controls
185 lines (155 loc) · 4.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
package pubgrub
import (
"fmt"
"sync"
)
// CachedSource wraps a Source and caches GetVersions and GetDependencies calls
// to improve performance when the same queries are made repeatedly.
//
// WHEN TO USE:
// CachedSource is most beneficial for:
// - Sources with expensive network I/O (package registries, APIs)
// - Sources with disk I/O or database queries
// - Running multiple dependency resolutions without recreating the source
// - Build systems that resolve dependencies repeatedly
//
// WHEN NOT TO USE:
// CachedSource adds ~3-5% overhead for:
// - InMemorySource (already fast, no repeated queries in CDCL solver)
// - Simple, single-shot dependency resolutions
// - Sources where queries are naturally cached upstream
//
// The cache is maintained for the lifetime of the CachedSource instance and
// assumes that version lists and dependencies are immutable during solving.
type CachedSource struct {
source Source
mu sync.Mutex
// Cache for GetVersions results
versionsCache map[Name][]Version
versionsCalls int
versionsCacheHits int
// Cache for GetDependencies results
depsCache map[string][]Term
depsCalls int
depsCacheHits int
}
// NewCachedSource creates a new caching wrapper around the given source.
func NewCachedSource(source Source) *CachedSource {
return &CachedSource{
source: source,
versionsCache: make(map[Name][]Version),
depsCache: make(map[string][]Term),
}
}
// GetVersions returns all available versions for a package, caching the result.
func (c *CachedSource) GetVersions(name Name) ([]Version, error) {
c.mu.Lock()
c.versionsCalls++
// Check cache first
if versions, ok := c.versionsCache[name]; ok {
c.versionsCacheHits++
out := cloneVersions(versions)
c.mu.Unlock()
return out, nil
}
c.mu.Unlock()
// Cache miss - fetch from underlying source
versions, err := c.source.GetVersions(name)
if err != nil {
return nil, err
}
cloned := cloneVersions(versions)
// Store in cache
c.mu.Lock()
c.versionsCache[name] = cloned
c.mu.Unlock()
return cloneVersions(cloned), nil
}
// GetDependencies returns dependencies for a specific package version, caching the result.
func (c *CachedSource) GetDependencies(name Name, version Version) ([]Term, error) {
c.mu.Lock()
c.depsCalls++
// Create cache key from name and version
key := fmt.Sprintf("%s@%s", name.Value(), version)
// Check cache first
if deps, ok := c.depsCache[key]; ok {
c.depsCacheHits++
out := cloneTerms(deps)
c.mu.Unlock()
return out, nil
}
c.mu.Unlock()
// Cache miss - fetch from underlying source
deps, err := c.source.GetDependencies(name, version)
if err != nil {
return nil, err
}
cloned := cloneTerms(deps)
// Store in cache
c.mu.Lock()
c.depsCache[key] = cloned
c.mu.Unlock()
return cloneTerms(cloned), nil
}
// CacheStats returns statistics about cache performance.
type CacheStats struct {
VersionsCalls int
VersionsCacheHits int
VersionsHitRate float64
DepsCalls int
DepsCacheHits int
DepsHitRate float64
TotalCalls int
TotalCacheHits int
OverallHitRate float64
}
// GetCacheStats returns cache performance statistics.
func (c *CachedSource) GetCacheStats() CacheStats {
c.mu.Lock()
stats := CacheStats{
VersionsCalls: c.versionsCalls,
VersionsCacheHits: c.versionsCacheHits,
DepsCalls: c.depsCalls,
DepsCacheHits: c.depsCacheHits,
TotalCalls: c.versionsCalls + c.depsCalls,
TotalCacheHits: c.versionsCacheHits + c.depsCacheHits,
}
c.mu.Unlock()
if stats.VersionsCalls > 0 {
stats.VersionsHitRate = float64(stats.VersionsCacheHits) / float64(stats.VersionsCalls)
}
if stats.DepsCalls > 0 {
stats.DepsHitRate = float64(stats.DepsCacheHits) / float64(stats.DepsCalls)
}
if stats.TotalCalls > 0 {
stats.OverallHitRate = float64(stats.TotalCacheHits) / float64(stats.TotalCalls)
}
return stats
}
// ClearCache clears all cached data while preserving the underlying source.
func (c *CachedSource) ClearCache() {
c.mu.Lock()
c.versionsCache = make(map[Name][]Version)
c.depsCache = make(map[string][]Term)
c.versionsCalls = 0
c.versionsCacheHits = 0
c.depsCalls = 0
c.depsCacheHits = 0
c.mu.Unlock()
}
func cloneVersions(in []Version) []Version {
if len(in) == 0 {
return nil
}
out := make([]Version, len(in))
copy(out, in)
return out
}
func cloneTerms(in []Term) []Term {
if len(in) == 0 {
return nil
}
out := make([]Term, len(in))
copy(out, in)
return out
}