Documentation
¶
Overview ¶
Package lock is mutual exclusion across processes: "only one replica runs this at a time".
Every correct distributed lock has four properties, and the naive implementation (SETNX, then DEL) has two of them. This one has all four:
- A TTL, so a crashed holder does not hold the lock forever.
- A unique token, so release only releases YOUR lock. Without it, a holder whose TTL expired mid-work deletes the lock a different replica has since acquired, and then two replicas are inside the critical section.
- A compare-and-delete release, done in Lua, because checking the token and deleting it as two commands is the same race one layer down.
- Optional refresh, for work that may outlive the TTL.
What it is NOT: a correctness guarantee against a Redis failover. If the primary dies after granting a lock and before replicating it, the replica can grant it again. Use this to prevent duplicate WORK, not to protect an invariant — for invariants, use a database transaction with a row lock.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNotAcquired = errors.New("lock: already held")
ErrNotAcquired is returned when the lock is held by somebody else.
var ErrNotHeld = errors.New("lock: no longer held")
ErrNotHeld is returned when releasing or refreshing a lock this process no longer owns — which means its TTL expired while the work was still running. Treat it as a signal that the TTL is too short, not as a nuisance.
Functions ¶
func Do ¶
func Do(ctx context.Context, l Locker, key string, ttl time.Duration, fn func(ctx context.Context) error) (ran bool, err error)
Do runs fn while holding key, and does nothing when the lock is held elsewhere.
The common use: a cron job running on three replicas where exactly one should do the work. ran is false when another replica had it.
Types ¶
type Lock ¶
Lock is one acquired lock.
type Locker ¶
type Locker interface {
// Acquire takes the lock, or returns ErrNotAcquired immediately.
Acquire(ctx context.Context, key string, ttl time.Duration) (*Lock, error)
}
Locker acquires locks.
type Memory ¶
type Memory struct {
// contains filtered or unexported fields
}
Memory is a single-process Locker.
Correct for one instance and for tests. Across replicas it enforces nothing — every replica has its own map — so do not reach for it to "get started" on work that will later run on more than one.
type Noop ¶
type Noop struct{}
Noop grants every lock. Use it to run a scheduler on exactly one replica by deployment rather than by coordination.
type RedisLocker ¶
type RedisLocker struct {
// contains filtered or unexported fields
}
RedisLocker is a Redis-backed Locker.
func NewRedis ¶
func NewRedis(client redis.UniversalClient, prefix string) *RedisLocker
NewRedis builds a locker. prefix namespaces the keys; set it to the service name so two services cannot take each other's locks.