discoverycache

package
v0.14.30 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: MIT Imports: 11 Imported by: 0

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

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

func (c *Cache) MaybeRefresh(s Source, fn Refresher)

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

func (c *Cache) MaybeRefreshSync(s Source, fn Refresher) error

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

func (c *Cache) Prune(activeSources []Source) error

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

func (c *Cache) Refresh(s Source, fn Refresher) error

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

func (c *Cache) SetWaitForRefreshHookForTesting(h func(Source))

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

type Refresher func(ctx context.Context) ([]byte, error)

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).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL