Documentation
¶
Index ¶
Constants ¶
const ( // DefaultTTL is the lease duration when WithTTL is not given. DefaultTTL = 30 * time.Second // DefaultRetryInterval is the polling cadence of a waiting Acquire. DefaultRetryInterval = 100 * time.Millisecond // MinAutoRenewTTL is the shortest lease supported by the renewal watchdog. // It leaves three renewal intervals inside one lease lifetime. MinAutoRenewTTL = 30 * time.Millisecond )
Default values applied when the corresponding Option is omitted.
Variables ¶
var ( // ErrNotAcquired indicates the lock is held by someone else and the // acquisition gave up (immediately, or after the WithWait window). ErrNotAcquired = errors.New("lock not acquired") // ErrNotHeld indicates a release or refresh on a lease that is no longer // owned — it expired, was released already, or was taken over. ErrNotHeld = errors.New("lock not held") // ErrAutoRenewTTLTooShort indicates the requested lease is too short for // the watchdog to renew without a tight spin loop. ErrAutoRenewTTLTooShort = errors.New("lock auto-renew TTL too short") )
Functions ¶
func WithLock ¶
func WithLock(ctx context.Context, locker Locker, name string, fn func(ctx context.Context) error, opts ...Option) (err error)
WithLock runs fn while holding the named lock: it acquires (auto-renewal on by default, so fn may safely outlive the TTL), cancels fn's context if the lease is lost, and always releases afterwards — even when fn panics — on a context that survives request cancellation. It returns the joined error of fn and the release — a successful fn still yields an error when the lease was lost mid-run, because the exclusive section can no longer be trusted.
Types ¶
type Lock ¶
type Lock interface {
// Release relinquishes the lease. It returns ErrNotHeld when the lease has
// already expired or been released — a signal that mutual exclusion may
// have been violated in the meantime.
Release(ctx context.Context) error
// Refresh extends the lease by the acquisition TTL from now. It returns
// ErrNotHeld once the lease is no longer owned. With auto-renewal enabled
// the lease refreshes itself and calling Refresh is unnecessary.
Refresh(ctx context.Context) error
// FencingToken returns the lease's monotonically increasing sequence
// number (unique and ordered per lock name). Pass it to the protected
// resource so a delayed writer holding a stale lease can be rejected.
FencingToken() int64
// Done returns a channel closed once the lease is known to be lost.
// Loss is detected by the auto-renewal watchdog, so without WithAutoRenew
// the channel never closes.
Done() <-chan struct{}
}
Lock is a held lease returned by a successful acquisition.
type Locker ¶
type Locker interface {
// Acquire obtains the named lock, retrying until the WithWait window is
// exhausted (no waiting by default). It returns ErrNotAcquired when the
// lock stays held by someone else, and fails closed on backend errors.
Acquire(ctx context.Context, name string, opts ...Option) (Lock, error)
// TryAcquire attempts a single non-blocking acquisition, returning
// ErrNotAcquired immediately when the lock is held — the natural guard for
// "only one replica runs this job" cron patterns.
TryAcquire(ctx context.Context, name string, opts ...Option) (Lock, error)
}
Locker acquires named distributed locks. Locks are lease-based: every acquisition carries a TTL that auto-expires if the holder crashes, and only the holder (identified by a random ownership token) can release or extend its lease. Mutual exclusion is therefore cooperative — a process pause that outlives the TTL can let a second holder in, so guard state that must never be corrupted with Lock.FencingToken, idempotency, or database constraints.
func NewMemoryLocker ¶
func NewMemoryLocker() Locker
NewMemoryLocker creates an empty in-process locker.
func NewRedisLocker ¶
NewRedisLocker creates a Redis-backed locker.
type MemoryLocker ¶
type MemoryLocker struct {
// contains filtered or unexported fields
}
MemoryLocker implements Locker with in-process state. It provides NO cross-replica mutual exclusion — it suits single-instance deployments and tests; multi-node deployments must use RedisLocker. Semantics (TTL expiry, ownership tokens, fencing tokens, waiting) match RedisLocker exactly so behavior does not change between environments.
func (*MemoryLocker) TryAcquire ¶
type Option ¶
type Option func(*acquireConfig)
Option customizes a single acquisition.
func WithAutoRenew ¶
WithAutoRenew toggles the background watchdog that refreshes the lease at a third of its TTL, so a healthy holder never expires mid-work while a crashed one still frees the lock within one TTL. Auto-renewal requires a TTL of at least MinAutoRenewTTL. It is off by default for bare Acquire / TryAcquire; WithLock turns it on unless explicitly disabled.
func WithRetryInterval ¶
WithRetryInterval sets the polling cadence used while waiting for a held lock. Non-positive values fall back to DefaultRetryInterval.
func WithTTL ¶
WithTTL sets the lease duration. The lease auto-expires this long after the acquisition (or the last refresh), bounding how long a crashed holder can block others. Non-positive values fall back to DefaultTTL. When auto-renewal is enabled, values below MinAutoRenewTTL fail with ErrAutoRenewTTLTooShort.
type RedisLocker ¶
type RedisLocker struct {
// contains filtered or unexported fields
}
RedisLocker implements Locker on a single Redis instance with the atomic Lua acquisition + token-guarded release/refresh pattern, giving cross-replica mutual exclusion to every node sharing the Redis. It is not a Redlock implementation: quorum locking over independent Redis nodes is out of scope, and the lease-based caveats documented on Locker apply.