patterns

package
v0.0.10 Latest Latest
Warning

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

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

Documentation

Overview

Package patterns provides small, optional helpers for common Valkey usage patterns. It is not a Redis ORM: there are no struct tags, no HASH repositories, and no replacement for valkey-go's command API. The chassis (cf_valkey.CFValkey) owns lifecycle, health, reload, key prefix, and metrics; this package adds a few sharp, prefix-aware helpers on top.

Allowlisted helpers:

  • Mutex: a single-instance distributed lock (SET NX PX + Lua unlock). Not Redlock. See the Mutex godoc for failure modes. Lock traffic is counted on the component's LockMeter and exposed via /metrics as lock_*_total counters.
  • GetJSON / SetJSON: marshal/unmarshal helpers with prefix-aware keys.
  • GetOrLoad: in-process singleflight cache-aside (one loader per key per process). Process-local only; other pods still stampede.

All helpers take a *cf_valkey.CFValkey (or a minimal interface) and use Client() + Key() so they are prefix-aware. Apps that only need a client never import this package.

Index

Constants

This section is empty.

Variables

View Source
var ErrLocked = errors.New("patterns: lock is held by another owner")

ErrLocked is returned by WithLock when TryLock finds the lock already held.

Functions

func GetJSON

func GetJSON(ctx context.Context, vk ClientKeyer, dst any, parts ...string) error

GetJSON retrieves the value at vk.Key(parts...) and unmarshals it from JSON into dst. Returns nil and no error when the key does not exist (Valkey nil reply). A non-nil error is returned for transport failures or JSON decode errors.

func GetOrLoad

func GetOrLoad(
	ctx context.Context,
	vk ClientKeyer,
	g *singleflight.Group,
	key string,
	ttl time.Duration,
	load LoadFunc,
) (val []byte, shared bool, err error)

GetOrLoad returns cached bytes for key (via vk.Key), or runs load once for concurrent callers in this process, then stores the result with ttl.

WARNING: process-local singleflight only. Other pods still stampede on a cold key. For cross-pod coalescing, compose with a Mutex.

shared=true when this caller waited on another goroutine's load.

Failure modes:

  • Many goroutines, one process, cold key: one load, others share.
  • Many pods, cold key: each pod may call load.
  • load fails: error returned to waiters; nothing cached.
  • Valkey down on GET: error returned (no silent bypass).
  • Valkey down on SET after load: loaded value returned along with the set error so callers can log; the successful load is not lost.

func SetJSON

func SetJSON(ctx context.Context, vk ClientKeyer, v any, ttl time.Duration, parts ...string) error

SetJSON marshals v as JSON and stores it at vk.Key(parts...) with the given TTL. The key is always prefix-aware through vk.Key.

Types

type ClientKeyer

type ClientKeyer interface {
	Client() valkey.Client
	Key(parts ...string) string
}

ClientKeyer is the minimal interface patterns requires from a Valkey component. *cf_valkey.CFValkey satisfies it.

type LoadFunc

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

LoadFunc fetches the canonical value on cache miss. Called at most once per key per in-flight wave inside this process.

type Mutex

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

Mutex is a small, single-instance distributed lock backed by Valkey. It uses SET NX PX for acquisition and a Lua script for ownership-checked deletion, so Unlock never removes another holder's lock.

Failure modes (read before use):

  • Single Valkey primary: at most one holder per key among clients talking to that instance. This is the default deployment assumption.
  • Holder crashes: lock expires after TTL; another pod may acquire. Size TTL >> expected critical section; << how long "double work" is acceptable.
  • Critical section outlives TTL: another pod can take the lock while the first still runs. Keep work << TTL.
  • Multi-master / split brain / Redlock: out of scope. Use a stronger coordination system if you need that.

This is not Redlock. It is for "only one pod should run this reconcile / refresh", not for substituting a consensus log.

func NewMutex

func NewMutex(vk ClientKeyer, name string, ttl time.Duration) *Mutex

NewMutex creates a distributed mutex. The lock key is always built through vk.Key("lock", name) so it sits in the component's prefix-aware key space. TTL is mandatory; there is no "lock forever" mode. When vk exposes a LockMeter (the *cf_valkey.CFValkey chassis does), lock traffic is counted and exposed on /metrics as *total counters.

func (*Mutex) TryLock

func (m *Mutex) TryLock(ctx context.Context) (bool, error)

TryLock attempts to acquire the lock. It returns ok=true if the lock was acquired, ok=false if another holder holds it. An error is returned only for transport-level failures (Valkey down, context cancelled).

func (*Mutex) Unlock

func (m *Mutex) Unlock(ctx context.Context) error

Unlock releases the lock only if the caller still owns it (token match). If the lock has expired or been stolen, Unlock returns nil without error (the lock is no longer ours to release). The outcome is counted on the component's lock meter (unlock_ok vs unlock_mismatch).

func (*Mutex) WithLock

func (m *Mutex) WithLock(ctx context.Context, fn func(context.Context) error) error

WithLock acquires the lock, runs fn, and releases the lock. If TryLock returns ok=false, fn is not called and WithLock returns ErrLocked. Unlock is best-effort: it runs even if fn fails, and ctx cancellation still attempts Unlock.

Jump to

Keyboard shortcuts

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