79 lines
1.8 KiB
Go
79 lines
1.8 KiB
Go
package usage
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const cacheTTL = 5 * time.Minute
|
|
|
|
type cachedEntry struct {
|
|
usage *ProviderUsage
|
|
fetchedAt time.Time
|
|
}
|
|
|
|
// UsageCache is a thread-safe in-memory cache for provider usage responses.
|
|
type UsageCache struct {
|
|
mu sync.RWMutex
|
|
entries map[string]*cachedEntry
|
|
}
|
|
|
|
// NewUsageCache creates an empty UsageCache.
|
|
func NewUsageCache() *UsageCache {
|
|
return &UsageCache{entries: make(map[string]*cachedEntry)}
|
|
}
|
|
|
|
// CacheKey builds a deterministic cache key from provider name and credential path.
|
|
func CacheKey(provider, credentialPath string) string {
|
|
h := sha256.Sum256([]byte(provider + ":" + credentialPath))
|
|
return fmt.Sprintf("%x", h[:8])
|
|
}
|
|
|
|
// GetOrFetch returns a cached entry if fresh; otherwise calls fetchFn, stores the
|
|
// result, and returns it. A nil result from fetchFn is stored as a negative cache
|
|
// entry so callers avoid hammering a provider that returned nothing.
|
|
func (c *UsageCache) GetOrFetch(
|
|
ctx context.Context,
|
|
key string,
|
|
fetchFn func(ctx context.Context) (*ProviderUsage, error),
|
|
) (*ProviderUsage, error) {
|
|
if usage := c.get(key); usage != nil {
|
|
return usage, nil
|
|
}
|
|
usage, err := fetchFn(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c.set(key, usage)
|
|
return usage, nil
|
|
}
|
|
|
|
func (c *UsageCache) get(key string) *ProviderUsage {
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
e, ok := c.entries[key]
|
|
if !ok {
|
|
return nil
|
|
}
|
|
if time.Since(e.fetchedAt) >= cacheTTL {
|
|
return nil
|
|
}
|
|
return e.usage
|
|
}
|
|
|
|
func (c *UsageCache) set(key string, usage *ProviderUsage) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
c.entries[key] = &cachedEntry{usage: usage, fetchedAt: time.Now()}
|
|
}
|
|
|
|
// Invalidate removes the cache entry for the given key.
|
|
func (c *UsageCache) Invalidate(key string) {
|
|
c.mu.Lock()
|
|
defer c.mu.Unlock()
|
|
delete(c.entries, key)
|
|
}
|