Documentation
¶
Overview ¶
Package negcache implements the per-puller, in-memory negative cache described in the design doc of the Gantry design.
When a puller's origin fetch fails terminally, the failure is classified (FailureAuth, FailureNotFound, FailureRateLimited, FailureTransient) and recorded against the digest. Subsequent pull_intent_query / please_pull RPCs for that digest must surface the cooldown state so requesters can short-circuit (the step: "Signal propagation via the existing probe RPCs").
Cooldown ladder (configurable, defaults from the design doc):
1st failure -> 10 s (Initial) 2nd failure -> 30 s (Initial × Multiplier) 3rd failure -> 90 s (Initial × Multiplier^2 capped at Max) 4th+ -> 10 min (Max)
The first successful pull clears the entry (the design doc "Self-healing").
The cache is local-only. The design (the design doc "Why the negative cache is local-only, not propagated via DHT") explicitly forbids cluster-wide propagation because a stale "this digest failed" marker outliving an actual recovery would be a serious correctness bug.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is the per-puller the design doc negative cache. Safe for concurrent use.
func New ¶
New returns an empty Cache. Required options that are zero get the the design doc defaults (Initial=10s, Max=10min, Multiplier=3, Now=time.Now).
func (*Cache) Len ¶
Len returns the current entry count. Cheap (single mutex acquisition) and side-effect-free: opportunistic eviction happens on the mutation and lookup paths, not here, so Len stays a pure read for gauges/tests.
func (*Cache) Lookup ¶
Lookup implements coord.NegativeCache. Returns (Entry, true) if a non-expired entry exists. Expired entries are evicted on access.
func (*Cache) RecordFailure ¶
RecordFailure registers a fresh terminal failure for d. The cooldown grows geometrically with successive failures, capped at Options.Max. Idempotent for the same call site; multiple calls within a single cooldown window keep extending it (the puller is expected to call this exactly once per terminal failure).
func (*Cache) RecordSuccess ¶
RecordSuccess clears any negative-cache entry for d. The the design doc spec says "the first successful pull of the digest clears the entry". Idempotent: a no-op when no entry exists.
type Entry ¶
type Entry struct {
LastFailure time.Time
FailureCount int
Class ifaces.FailureClass
CooldownUntil time.Time
}
Entry mirrors the design doc's recent_failures[digest] record.
type Options ¶
type Options struct {
// Initial is the cooldown applied on the first failure.
Initial time.Duration
// Max caps the cooldown after exponential growth.
Max time.Duration
// Multiplier is the geometric factor between successive cooldowns.
// Spec default is 3× (10 s -> 30 s -> 90 s -> ... -> Max).
Multiplier int
// Now is the clock used for cooldown comparisons. Tests override to
// inject a deterministic time source. Defaults to time.Now.
Now func() time.Time
// SweepInterval controls how often ordinary cache operations do a full
// expired-entry sweep. Zero picks the default; negative disables full
// opportunistic sweeps (Lookup still evicts the requested key).
SweepInterval time.Duration
// OnEnter and OnHit are optional metric callbacks. nil-safe.
OnEnter func(class ifaces.FailureClass)
OnHit func(class ifaces.FailureClass)
// OnSize is called after every mutation with the new entry count
// so a GaugeFunc reader can expose `p2p_negative_cache_entries`
// without locking the map. nil-safe.
OnSize func(count int)
}
Options configures the cooldown ladder. Zero values pick the defaults; callers should still pass an explicit value to make tests deterministic.