Documentation
¶
Overview ¶
Package discoverycache implements the per-source on-disk cache described in ADR-012. It provides a two-tier lock + long-lived refresh marker pattern so that slow discovery IO (PTY ~30 s, HTTP ~316 ms) never blocks UI reads.
Public surface: Cache.Read, Cache.Refresh, Cache.MaybeRefresh, Cache.Prune. The Refresher function is injected; the cache manages single-flight deduplication, atomic writes, and crash recovery.
Index ¶
- type Cache
- func (c *Cache) MaybeRefresh(s Source, fn Refresher)
- func (c *Cache) MaybeRefreshSync(s Source, fn Refresher) error
- func (c *Cache) Prune(activeSources []Source) error
- func (c *Cache) Read(s Source) (ReadResult, error)
- func (c *Cache) Refresh(s Source, fn Refresher) error
- func (c *Cache) RefreshState(s Source) (RefreshState, error)
- func (c *Cache) SetWaitForRefreshHookForTesting(h func(Source))
- type ReadResult
- type RefreshState
- type Refresher
- type Source
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
Root string
// contains filtered or unexported fields
}
Cache is the root handle. Root is the base directory (e.g. ~/.cache/fizeau). Each Cache instance owns one in-process singleflight.Group; a new Cache per process is the normal usage.
func (*Cache) MaybeRefresh ¶
MaybeRefresh triggers a background refresh when the data is stale or absent, then returns immediately. The caller always gets data from Read; MaybeRefresh only schedules the replenishment. Claiming happens synchronously so repeated stale reads do not fan out duplicate background goroutines.
func (*Cache) MaybeRefreshSync ¶ added in v0.13.1
MaybeRefreshSync refreshes synchronously only when the cache is stale or absent. Returns the refresh error (nil if data was fresh or the refresh succeeded). Composes with single-flight: concurrent callers share one refreshAndCommit, both in-process (singleflight.Group) and cross-process (the file marker). This is for explicit refresh/preflight surfaces; route hot paths use MaybeRefresh so stale providers cannot block scoring.
func (*Cache) Prune ¶
Prune removes data files (and their sidecar lock/marker/tmp files) for sources not listed in activeSources. Sources with an active .refreshing marker are skipped. Prune acquires the tier-1 lock before removing each source's files.
func (*Cache) Read ¶
func (c *Cache) Read(s Source) (ReadResult, error)
Read returns cached data without any IO beyond reading the local file. It never blocks on network or PTY. If no cache file exists, Data is nil and Stale is true. Stale data is always returned without error; the caller decides whether to trigger a background refresh.
func (*Cache) Refresh ¶
Refresh forces a synchronous refresh regardless of TTL. If a refresh is already in flight (another goroutine or process), Refresh waits for it to complete. In-process callers share one goroutine via singleflight.
func (*Cache) RefreshState ¶ added in v0.12.3
func (c *Cache) RefreshState(s Source) (RefreshState, error)
RefreshState reports the current refresh marker status for s.
func (*Cache) SetWaitForRefreshHookForTesting ¶ added in v0.14.29
SetWaitForRefreshHookForTesting lets tests observe when Refresh joins an already active marker-owned refresh and begins waiting for it to finish.
type ReadResult ¶
type ReadResult struct {
// Data is the raw bytes from the cache file; nil if no file exists.
Data []byte
// Age is how long ago the data was written (0 if absent).
Age time.Duration
// Fresh is true when Age < TTL and Data is non-nil.
Fresh bool
// Stale is true when Data is nil or Age >= TTL.
Stale bool
}
ReadResult is the output of Cache.Read.
type RefreshState ¶ added in v0.12.3
type RefreshState struct {
InFlight bool
Failed bool
LastError string
StartedAt time.Time
Deadline time.Time
}
RefreshState reports the current marker state for a source. It is used by higher layers to surface refresh_failed / in-flight diagnostics without re-reading raw marker files.
type Refresher ¶
Refresher is the source-fetch function injected by the caller. The cache handles single-flight deduplication, the atomic write, and marker lifecycle.
type Source ¶
type Source struct {
// Tier is "discovery" or "runtime".
Tier string
// Name is the kebab-case source identifier (e.g. "openrouter").
Name string
// TTL controls freshness: Read returns Fresh=true when Age < TTL.
TTL time.Duration
// RefreshDeadline is the maximum time a refresh is expected to take.
// Also controls the staleness threshold: 2 × RefreshDeadline.
RefreshDeadline time.Duration
}
Source describes one cacheable data stream (one file under Root/Tier/Name.json).