summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-19 08:15:28 +0300
committerPaul Buetow <paul@buetow.org>2026-06-19 08:15:28 +0300
commit682a413db516e1a0201e8fea41e706a7666391d6 (patch)
tree614566bce20aacd4935aa81fd9c49aca523a2113
parenta80ad2b4691d0df63f68c6977ee18444e7bb752f (diff)
ik0 remove mutable clock holders
-rw-r--r--internal/hexaicli/cache.go11
-rw-r--r--internal/stats/stats.go12
2 files changed, 7 insertions, 16 deletions
diff --git a/internal/hexaicli/cache.go b/internal/hexaicli/cache.go
index 742ffce..127c7f4 100644
--- a/internal/hexaicli/cache.go
+++ b/internal/hexaicli/cache.go
@@ -17,17 +17,12 @@ const cliResponseCacheTTL = 24 * time.Hour
// responseCache carries the injectable dependencies for the on-disk CLI
// response cache. The only dependency is the clock used to stamp entries and
-// decide expiry. Production code uses defaultResponseCache (backed by
-// time.Now); tests construct a responseCache with a fake clock to exercise TTL
-// expiry without sleeping.
+// decide expiry. Production wrappers construct it with time.Now; tests can use
+// a fake clock to exercise TTL expiry without sleeping.
type responseCache struct {
now func() time.Time
}
-// defaultResponseCache is the production cache used by the package-level
-// lookup/store wrappers. It reads the real wall clock.
-var defaultResponseCache = responseCache{now: time.Now}
-
// cacheNowContextKey carries an injected clock through the request context so
// the cache TTL logic can be driven deterministically (e.g. in tests) without
// mutating package state.
@@ -45,7 +40,7 @@ func responseCacheFromContext(ctx context.Context) responseCache {
if now, ok := ctx.Value(cacheNowContextKey{}).(func() time.Time); ok && now != nil {
return responseCache{now: now}
}
- return defaultResponseCache
+ return responseCache{now: time.Now}
}
type cliResponseCacheKey struct {
diff --git a/internal/stats/stats.go b/internal/stats/stats.go
index 65d1b2d..16327f6 100644
--- a/internal/stats/stats.go
+++ b/internal/stats/stats.go
@@ -31,16 +31,12 @@ var windowSeconds int64 = int64(defaultWindow.Seconds())
// engine carries the injectable dependencies for stats operations. The only
// dependency today is the clock source for event timestamps and pruning
-// cutoffs. Production code uses defaultEngine (backed by time.Now); tests
-// construct an engine with a fake clock to control time without sleeping.
+// cutoffs. Production wrappers construct it with time.Now; tests can use a fake
+// clock to control time without sleeping.
type engine struct {
now func() time.Time
}
-// defaultEngine is the production engine used by the package-level Update and
-// TakeSnapshot wrappers. It reads the real wall clock.
-var defaultEngine = engine{now: time.Now}
-
// SetWindow sets the sliding window used for pruning and aggregation.
func SetWindow(d time.Duration) {
if d < time.Second {
@@ -115,7 +111,7 @@ func (s Snapshot) ScopeRPM(provider, model string) float64 {
// Update appends one event and prunes old entries under lock, using the
// production clock. It delegates to engine.update.
func Update(ctx context.Context, provider, model string, sentBytes, recvBytes int) error {
- return defaultEngine.update(ctx, provider, model, sentBytes, recvBytes)
+ return engine{now: time.Now}.update(ctx, provider, model, sentBytes, recvBytes)
}
// update appends one event and prunes old entries under lock.
@@ -232,7 +228,7 @@ func writeStatsFileAtomic(dir, path string, sf *File) error {
// TakeSnapshot reads the stats file and aggregates events within the stored
// window, using the production clock. It delegates to engine.takeSnapshot.
func TakeSnapshot() (Snapshot, error) {
- return defaultEngine.takeSnapshot()
+ return engine{now: time.Now}.takeSnapshot()
}
// takeSnapshot reads the stats file and aggregates events within the stored