state

package
v1.15.0 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package state abstracts the gateway's shared runtime state — rate-limit windows, circuit-breaker state, and list caches — behind interfaces with two providers: in-process (default) and Redis (set REDIS_URL or server.redisUrl), which makes a fleet of gateway instances behave as one.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Breaker

type Breaker interface {
	Allow(ctx context.Context) bool
	Record(ctx context.Context, success bool)
	State(ctx context.Context) breaker.State
}

Breaker is a shared circuit breaker.

type Budget added in v1.7.0

type Budget interface {
	// Add records n units of consumption and reports whether they fit. A
	// rejected Add records nothing — an over-budget caller does not dig the
	// hole deeper by retrying.
	Add(ctx context.Context, n int64) BudgetResult
	// Used reports the window's state without consuming any of it. A budget
	// that can only reject is one an operator discovers at 100%; this is what
	// makes 80% visible.
	Used(ctx context.Context) BudgetResult
}

Budget admits consumption against a fixed, calendar-aligned allowance that resets at the period boundary.

Unlike Limiter, which smooths a rate, a Budget accumulates: what is spent stays spent until the window rolls over.

type BudgetResult added in v1.7.0

type BudgetResult struct {
	// Allowed reports whether the consumption fits the allowance.
	Allowed bool
	// Used and Limit describe the window after this check. Limit is 0 for an
	// unlimited budget.
	Used, Limit int64
	// Resets is when the window rolls over. It is not a retry delay: the
	// answer to an exhausted monthly budget is "not until the 1st", and a
	// caller that backs off by this amount would sleep for a fortnight.
	Resets time.Time
	// Degraded reports that shared state was unreachable, so this decision
	// was made per-instance rather than fleet-wide. Budgets fail open — a
	// dependency blip must not refuse all service — but silence would let a
	// fleet run unbudgeted without anyone noticing, so the caller is expected
	// to surface this in audit and metrics.
	Degraded bool
}

A BudgetResult is the outcome of a budget check.

type Limiter

type Limiter interface {
	// Allow reports whether one more request fits and, when it does not,
	// how long to wait before retrying.
	Allow(ctx context.Context) (ok bool, retryAfter time.Duration)
}

Limiter admits requests against a shared budget.

type ListCache

type ListCache interface {
	// GetOrFill returns the cached bytes for key, or runs fill and caches
	// its result for ttl. ttl <= 0 bypasses caching.
	GetOrFill(ctx context.Context, key string, ttl time.Duration, fill func(context.Context) ([]byte, error)) ([]byte, error)
	// Invalidate drops entries whose key has the given prefix.
	Invalidate(ctx context.Context, prefix string)
}

ListCache caches serialized list results with a TTL, invalidated by prefix.

type MemOnce

type MemOnce struct {
	// contains filtered or unexported fields
}

MemOnce is the in-process single-use recorder. Exported so the Redis provider can fall back to it during an outage.

func NewMemOnce

func NewMemOnce() *MemOnce

NewMemOnce returns an in-process single-use recorder.

func (*MemOnce) TryOnce

func (o *MemOnce) TryOnce(_ context.Context, key string, ttl time.Duration) bool

TryOnce reports whether key is fresh, recording it for ttl. Expired records read as absent and are reclaimed by the bounded map itself — no full scan per call. A non-positive ttl records the key until evicted (bounded.Map semantics); callers wanting single-use enforcement pass the credential's real remaining lifetime, as the EMA path does.

type MemStore added in v1.5.0

type MemStore struct {
	// contains filtered or unexported fields
}

MemStore is the in-process record store. Exported so the Redis store can mirror into it and fall back on it during an outage.

func NewMemStore added in v1.5.0

func NewMemStore() *MemStore

NewMemStore returns an empty in-process record store.

func (*MemStore) Delete added in v1.5.0

func (s *MemStore) Delete(_ context.Context, key string)

Delete implements Store.

func (*MemStore) Get added in v1.5.0

func (s *MemStore) Get(_ context.Context, key string) ([]byte, bool)

Get implements Store.

func (*MemStore) GetMany added in v1.5.0

func (s *MemStore) GetMany(_ context.Context, keys []string) map[string][]byte

GetMany implements Store.

func (*MemStore) Set added in v1.5.0

func (s *MemStore) Set(_ context.Context, key string, value []byte, ttl time.Duration)

Set implements Store.

func (*MemStore) SetMany added in v1.5.0

func (s *MemStore) SetMany(_ context.Context, entries map[string][]byte, ttl time.Duration)

SetMany implements Store.

type Memory

type Memory struct{}

Memory is the in-process provider; state is per gateway instance.

func NewMemory

func NewMemory() *Memory

NewMemory returns the in-process state provider.

func (*Memory) Breaker

func (*Memory) Breaker(_ string, threshold int, halfOpenAfter time.Duration) Breaker

Breaker implements Provider.

func (*Memory) Budget added in v1.7.0

func (*Memory) Budget(_ string, period Period, limit int64) Budget

Budget implements Provider.

func (*Memory) Close

func (*Memory) Close() error

Close implements Provider.

func (*Memory) Limiter

func (*Memory) Limiter(_ string, rpm int) Limiter

Limiter implements Provider.

func (*Memory) ListCache

func (*Memory) ListCache(_ string) ListCache

ListCache implements Provider.

func (*Memory) Once

func (*Memory) Once(_ string) Once

Once implements Provider.

func (*Memory) Store added in v1.5.0

func (*Memory) Store(_ string) Store

Store implements Provider.

type Once

type Once interface {
	// TryOnce records key for ttl and reports whether this was the first
	// use. false means the key was already recorded — a replay.
	TryOnce(ctx context.Context, key string, ttl time.Duration) bool
}

Once records single-use keys (e.g. token replay protection).

type Period added in v1.7.0

type Period string

A Period is a calendar-aligned budget window.

Calendar alignment is the whole point, and the reason a budget cannot be a Limiter with a longer window: that limiter is a two-bucket sliding window, so an exhausted caller is readmitted gradually as the trailing window elapses. A budget must instead reset at a boundary an operator can predict, explain to a customer, and reconcile against.

Boundaries are UTC. A local-time month would move under a DST transition and differ between instances in different zones, which for a fleet-wide counter means two instances disagreeing about which month it is.

const (
	PeriodHour  Period = "hour"
	PeriodDay   Period = "day"
	PeriodMonth Period = "month"
)

The supported budget periods.

func (Period) Valid added in v1.7.0

func (p Period) Valid() bool

Valid reports whether p is a known period.

type Provider

type Provider interface {
	// Limiter returns a rate limiter for scope admitting rpm requests per
	// minute. rpm <= 0 returns an unlimited limiter.
	Limiter(scope string, rpm int) Limiter
	// Budget returns an accumulating allowance for scope over a
	// calendar-aligned period. limit <= 0 returns an unlimited budget.
	Budget(scope string, period Period, limit int64) Budget
	// Breaker returns a circuit breaker for scope.
	Breaker(scope string, threshold int, halfOpenAfter time.Duration) Breaker
	// ListCache returns the list cache for scope.
	ListCache(scope string) ListCache
	// Once returns the single-use recorder for scope.
	Once(scope string) Once
	// Store returns the shared record store for scope.
	Store(scope string) Store
	// Close releases provider resources.
	Close() error
}

Provider constructs the gateway's shared-state primitives.

type Redis

type Redis struct {
	// contains filtered or unexported fields
}

Redis is the shared-state provider backed by a Redis instance; a fleet of gateways pointed at the same Redis behaves as one gateway for rate limits, circuit breakers, and list caches.

func NewRedis

func NewRedis(url string) (*Redis, error)

NewRedis connects and validates a Redis provider from a redis:// URL.

func (*Redis) Breaker

func (r *Redis) Breaker(scope string, threshold int, halfOpenAfter time.Duration) Breaker

Breaker implements Provider.

func (*Redis) Budget added in v1.7.0

func (r *Redis) Budget(scope string, period Period, limit int64) Budget

Budget implements Provider.

func (*Redis) Close

func (r *Redis) Close() error

Close implements Provider.

func (*Redis) Limiter

func (r *Redis) Limiter(scope string, rpm int) Limiter

Limiter implements Provider.

func (*Redis) ListCache

func (r *Redis) ListCache(scope string) ListCache

ListCache implements Provider.

func (*Redis) OnDegraded added in v1.11.0

func (r *Redis) OnDegraded(fn func(kind string))

OnDegraded registers fn to be told each time a rate-limit or breaker decision was made per-instance because Redis was unreachable (kind: "limiter" | "breaker"). The gateway wires it into fold_state_degraded_total — fail-open is deliberate, but a fleet whose limits are momentarily per-instance must be visible, exactly as budgets already are. Safe to call before or after limiters exist.

func (*Redis) Once

func (r *Redis) Once(scope string) Once

Once implements Provider.

func (*Redis) Store added in v1.5.0

func (r *Redis) Store(scope string) Store

Store implements Provider.

type Store added in v1.5.0

type Store interface {
	// Get returns the record for key. Absent — including when the backing
	// store is unreachable — reads as (nil, false).
	Get(ctx context.Context, key string) ([]byte, bool)
	// GetMany returns the records present among keys, omitting absent ones.
	GetMany(ctx context.Context, keys []string) map[string][]byte
	// Set writes key for ttl (ttl <= 0 never expires).
	Set(ctx context.Context, key string, value []byte, ttl time.Duration)
	// SetMany writes every entry with the same ttl in one round trip.
	SetMany(ctx context.Context, entries map[string][]byte, ttl time.Duration)
	// Delete removes key.
	Delete(ctx context.Context, key string)
}

Store holds small opaque records, each with its own TTL. Unlike ListCache it is not a fill-through cache: absence is meaningful to the caller and writes are explicit. Used for records a fleet must agree on rather than each instance observing for itself — task ownership, today.

The batch forms exist because the federated task list resolves ownership for a whole page at once; issuing one round trip per task would make list latency scale with the size of the federation's task set.

Jump to

Keyboard shortcuts

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