Documentation
¶
Overview ¶
Package redislock provides a Redis-based distributed lock for the Lynx framework.
It supports standalone, Cluster, and Sentinel via redis.UniversalClient. Features include: atomic acquire/release/renew via Lua scripts, reentrancy per lock instance, optional auto-renewal, retry with jitter, and fencing token (see LockWithToken). For design limits (single-node vs Redlock, process pause, renewal failure), see LIMITATIONS.md.
Index ¶
- Constants
- Variables
- func GetErrorMessage(code, lang string) string
- func GetStats() map[string]int64
- func InitMetrics(reg prometheus.Registerer)
- func IsLockError(err error, code string) bool
- func Lock(ctx context.Context, key string, expiration time.Duration, fn func() error) error
- func LockWithOptions(ctx context.Context, key string, options LockOptions, fn func() error) (retErr error)
- func LockWithRetry(ctx context.Context, key string, expiration time.Duration, fn func() error, ...) error
- func LockWithToken(ctx context.Context, key string, expiration time.Duration, ...) (retErr error)
- func SetCallback(callback LockCallback)
- func Shutdown(ctx context.Context) error
- func UnlockByValue(ctx context.Context, key, value string) error
- func ValidateKey(key string) error
- type LockCallback
- type LockError
- type LockOptions
- type NoOpCallback
- func (NoOpCallback) OnLockAcquireFailed(key string, error error)
- func (NoOpCallback) OnLockAcquired(key string, duration time.Duration)
- func (NoOpCallback) OnLockReleased(key string, duration time.Duration)
- func (NoOpCallback) OnLockRenewalFailed(key string, error error)
- func (NoOpCallback) OnLockRenewed(key string, duration time.Duration)
- type Provider
- type RedisLock
- func (rl *RedisLock) Acquire(ctx context.Context) error
- func (rl *RedisLock) AcquireWithRetry(ctx context.Context, strategy RetryStrategy) error
- func (rl *RedisLock) EnableAutoRenew(options LockOptions)
- func (rl *RedisLock) GetAcquiredAt() time.Time
- func (rl *RedisLock) GetExpiration() time.Duration
- func (rl *RedisLock) GetExpiresAt() time.Time
- func (rl *RedisLock) GetKey() string
- func (rl *RedisLock) GetRemainingTime() time.Duration
- func (rl *RedisLock) GetStatus() (remainingTime time.Duration, isExpired bool)
- func (rl *RedisLock) GetToken() int64
- func (rl *RedisLock) IsExpired() bool
- func (rl *RedisLock) IsLocked(ctx context.Context) (bool, error)
- func (rl *RedisLock) Release(ctx context.Context) error
- func (rl *RedisLock) Renew(ctx context.Context, newExpiration time.Duration) error
- type RenewalConfig
- type RetryStrategy
Constants ¶
const ( ErrCodeLockNotHeld = "LOCK_NOT_HELD" ErrCodeLockAcquireFailed = "LOCK_ACQUIRE_FAILED" ErrCodeLockAcquireTimeout = "LOCK_ACQUIRE_TIMEOUT" ErrCodeLockAcquireConflict = "LOCK_ACQUIRE_CONFLICT" ErrCodeRedisClientNotFound = "REDIS_CLIENT_NOT_FOUND" ErrCodeMaxRetriesExceeded = "MAX_RETRIES_EXCEEDED" ErrCodeLockFnRequired = "LOCK_FN_REQUIRED" ErrCodeLockRenewalFailed = "LOCK_RENEWAL_FAILED" ErrCodeRenewalServiceStopped = "RENEWAL_SERVICE_STOPPED" ErrCodeInvalidOptions = "INVALID_OPTIONS" )
Error code definitions
const MaxLockKeyLength = 255
MaxLockKeyLength is the maximum allowed length for a lock key (business key, not the internal Redis key).
Variables ¶
var ( // ErrLockNotHeld indicates attempting to release a lock not held ErrLockNotHeld = newLockError(ErrCodeLockNotHeld, "lock not held", nil) // ErrLockAcquireFailed indicates lock acquisition failure ErrLockAcquireFailed = newLockError(ErrCodeLockAcquireFailed, "failed to acquire lock", nil) // ErrLockAcquireTimeout indicates lock acquisition timeout ErrLockAcquireTimeout = newLockError(ErrCodeLockAcquireTimeout, "lock acquire timeout", nil) // ErrLockAcquireConflict indicates lock acquisition conflict ErrLockAcquireConflict = newLockError(ErrCodeLockAcquireConflict, "lock acquire conflict", nil) // ErrRedisClientNotFound indicates Redis client not found ErrRedisClientNotFound = newLockError(ErrCodeRedisClientNotFound, "redis client not found", nil) // ErrMaxRetriesExceeded indicates exceeding maximum retry attempts ErrMaxRetriesExceeded = newLockError(ErrCodeMaxRetriesExceeded, "max retries exceeded", nil) // ErrLockFnRequired indicates lock protected function cannot be empty ErrLockFnRequired = newLockError(ErrCodeLockFnRequired, "lock function is required", nil) // ErrLockRenewalFailed indicates lock renewal failure ErrLockRenewalFailed = newLockError(ErrCodeLockRenewalFailed, "lock renewal failed", nil) // ErrRenewalServiceStopped indicates renewal service has stopped ErrRenewalServiceStopped = newLockError(ErrCodeRenewalServiceStopped, "renewal service stopped", nil) // ErrInvalidOptions indicates invalid configuration options ErrInvalidOptions = newLockError(ErrCodeInvalidOptions, "invalid options", nil) )
var ( DefaultRetryStrategy = RetryStrategy{ MaxRetries: 3, RetryDelay: 100 * time.Millisecond, } DefaultRenewalConfig = RenewalConfig{ MaxRetries: 4, BaseDelay: 100 * time.Millisecond, MaxDelay: 800 * time.Millisecond, CheckInterval: 300 * time.Millisecond, CallTimeout: 600 * time.Millisecond, } DefaultLockOptions LockOptions )
Default configurations
Functions ¶
func GetErrorMessage ¶
GetErrorMessage gets internationalized error message
func InitMetrics ¶
func InitMetrics(reg prometheus.Registerer)
InitMetrics registers the collectors to the provided Registerer. Pass nil to use the default Registerer.
func Lock ¶
Lock acquires a distributed lock for the specified key and executes the callback function, automatically releasing the lock after execution. - Uses DefaultLockOptions as base configuration, only overriding Expiration. - Uses Lua script for atomic lock acquisition/reentrancy, avoiding race conditions. - If renewal is enabled, registers in global manager and automatically renews until function execution ends.
func LockWithOptions ¶
func LockWithOptions(ctx context.Context, key string, options LockOptions, fn func() error) (retErr error)
LockWithOptions uses complete configuration options to acquire lock and execute callback function. Key behaviors:
- Delegates acquisition and retry to lock.AcquireWithRetry, which calls Acquire once per attempt.
- After successful acquisition, if renewal is enabled, register in global manager and start renewal service.
- Release is performed via defer on an independent short-timeout context so a cancelled business ctx cannot block best-effort release. Release.go already calls removeManagedLock on full release, so no extra IsLocked round-trip is needed.
Errors:
- Acquisition failures due to contention trigger OnLockAcquireFailed callback inside Acquire and are subject to the retry strategy; other errors are returned immediately.
func LockWithRetry ¶
func LockWithRetry(ctx context.Context, key string, expiration time.Duration, fn func() error, strategy RetryStrategy) error
LockWithRetry acquires lock and executes function, supports retry by strategy. - Based on DefaultLockOptions, overrides Expiration and RetryStrategy, others use defaults. - Uses random jitter (0.5~1.5x) during retries to reduce hot spot collisions.
func LockWithToken ¶
func LockWithToken(ctx context.Context, key string, expiration time.Duration, fn func(token int64) error) (retErr error)
LockWithToken acquires a distributed lock and runs fn; the callback receives a fencing token (see LIMITATIONS.md). - Based on DefaultLockOptions, only overriding Expiration, retry strategy uses DefaultRetryStrategy. - token is only incremented on "first acquisition" (non-reentrant); reentry does not generate a new token.
func SetCallback ¶
func SetCallback(callback LockCallback)
SetCallback installs the process-wide lock-event callback; nil resets to no-op.
func Shutdown ¶
Shutdown gracefully shuts down the lock manager. It stops the renewal service and polls until all active locks are released or the context is cancelled. Callers control the deadline via ctx — e.g.:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() _ = redislock.Shutdown(ctx)
func UnlockByValue ¶
UnlockByValue releases lock using key + value method (no need to hold RedisLock instance). Semantic explanation:
- When count > 0, this operation is a "partial release". This implementation uniformly passes TTL=0 to the script, indicating not to refresh TTL (keeping the remaining expiration time unchanged).
- When key does not exist or value does not match, returns ErrLockNotHeld.
Timeout explanation: - Single script call uses DefaultLockOptions.ScriptCallTimeout as optional per-call timeout.
func ValidateKey ¶
ValidateKey validates the lock key: non-empty, length <= MaxLockKeyLength, printable ASCII, no '{' or '}'.
Types ¶
type LockCallback ¶
type LockCallback interface {
OnLockAcquired(key string, duration time.Duration)
OnLockReleased(key string, duration time.Duration)
OnLockRenewed(key string, duration time.Duration)
OnLockRenewalFailed(key string, error error)
OnLockAcquireFailed(key string, error error)
}
LockCallback lock operation callback interface
type LockOptions ¶
type LockOptions struct {
Expiration time.Duration // Lock expiration time
RetryStrategy RetryStrategy // Retry strategy
RenewalEnabled bool // Whether to enable auto renewal
RenewalThreshold float64 // Renewal threshold (proportion relative to expiration time, default 1/3)
WorkerPoolSize int // Renewal worker pool size, default 50
RenewalConfig RenewalConfig // Renewal configuration
// ScriptCallTimeout timeout control for single script call (acquire/release). 0 means no separate timeout.
ScriptCallTimeout time.Duration
// TokenTTL is the TTL applied to the fencing-token counter key in Redis.
// The counter must outlive individual lock sessions to keep tokens monotonically increasing.
// Defaults to 7 days. Set to 0 to use the default.
TokenTTL time.Duration
}
LockOptions configures lock behavior: expiration, retry, renewal, and script timeouts.
func (*LockOptions) Validate ¶
func (lo *LockOptions) Validate() error
Validate validates the lock options (expiration, renewal threshold, worker pool size, retry strategy).
type NoOpCallback ¶
type NoOpCallback struct{}
NoOpCallback empty implementation callback
func (NoOpCallback) OnLockAcquireFailed ¶
func (NoOpCallback) OnLockAcquireFailed(key string, error error)
func (NoOpCallback) OnLockAcquired ¶
func (NoOpCallback) OnLockAcquired(key string, duration time.Duration)
func (NoOpCallback) OnLockReleased ¶
func (NoOpCallback) OnLockReleased(key string, duration time.Duration)
func (NoOpCallback) OnLockRenewalFailed ¶
func (NoOpCallback) OnLockRenewalFailed(key string, error error)
func (NoOpCallback) OnLockRenewed ¶
func (NoOpCallback) OnLockRenewed(key string, duration time.Duration)
type Provider ¶ added in v1.6.1
type Provider interface {
NewLock(ctx context.Context, key string, options LockOptions) (*RedisLock, error)
Lock(ctx context.Context, key string, expiration time.Duration, fn func() error) error
LockWithOptions(ctx context.Context, key string, options LockOptions, fn func() error) error
LockWithToken(ctx context.Context, key string, expiration time.Duration, fn func(token int64) error) error
UnlockByValue(ctx context.Context, key, value string) error
}
Provider exposes redis-lock through an injectable facade while resolving the underlying Redis client via lynx-redis's stable provider on each call.
func GetProvider ¶ added in v1.6.1
func GetProvider() Provider
GetProvider returns the injectable redis lock facade.
type RedisLock ¶
type RedisLock struct {
// contains filtered or unexported fields
}
RedisLock implements Redis-based distributed lock. provider resolves the current UniversalClient on demand so long-lived lock handles do not cache a replaceable Redis client across reconnects or managed restarts.
func NewLock ¶
NewLock creates a reusable lock instance (supports reentrancy within the same instance). Behavior: - Does not actively trigger locking, only builds RedisLock object; caller must explicitly call Acquire() to obtain or reenter lock. - Multiple Acquire calls on the same instance are treated as reentrant by the script due to unchanged value, and TTL is refreshed. - Redis Cluster: internal ownerKey and countKey use the same hashtag to ensure same slot for Lua atomic operations.
func (*RedisLock) Acquire ¶
Acquire attempts to acquire (or reenter) the lock based on the current RedisLock instance. If called again on the same instance, the Lua script treats it as reentrant and renews the TTL because the value remains unchanged. The fencing token is incremented atomically inside the script on first acquisition; no separate Redis round-trip is needed.
func (*RedisLock) AcquireWithRetry ¶
func (rl *RedisLock) AcquireWithRetry(ctx context.Context, strategy RetryStrategy) error
AcquireWithRetry acquires (or reenters) the lock and retries according to strategy
func (*RedisLock) EnableAutoRenew ¶
func (rl *RedisLock) EnableAutoRenew(options LockOptions)
EnableAutoRenew registers the current lock to the global renewal manager (starts if not already started)
func (*RedisLock) GetAcquiredAt ¶
GetAcquiredAt returns when the lock was acquired (guarded by mutex for consistency with renewal).
func (*RedisLock) GetExpiration ¶
GetExpiration returns the configured lock TTL.
func (*RedisLock) GetExpiresAt ¶
GetExpiresAt returns the absolute expiration time (guarded by mutex).
func (*RedisLock) GetRemainingTime ¶
GetRemainingTime returns the remaining TTL until expiry (guarded by mutex).
func (*RedisLock) GetStatus ¶
GetStatus returns remaining TTL and whether the lock is already expired (single snapshot under mutex).
func (*RedisLock) GetToken ¶
GetToken returns the most recently acquired fencing token (generated on non-reentrant acquisition). If 0, the lock has not been acquired for the first time in this process (or only reentry occurred). Fencing semantics: the resource layer must reject requests with an older token. See LIMITATIONS.md.
func (*RedisLock) IsExpired ¶
IsExpired reports whether the lock’s local expiry time has passed (guarded by mutex).
func (*RedisLock) IsLocked ¶
IsLocked returns whether the current instance holds the lock in Redis (by value match).
type RenewalConfig ¶
type RenewalConfig struct {
MaxRetries int // Maximum renewal retry attempts
BaseDelay time.Duration // Base retry delay
MaxDelay time.Duration // Maximum retry delay
CheckInterval time.Duration // Renewal check interval
// CallTimeout single renewal script call timeout. 0 means no separate timeout.
CallTimeout time.Duration
}
RenewalConfig renewal configuration
type RetryStrategy ¶
type RetryStrategy struct {
MaxRetries int // Maximum retry attempts
RetryDelay time.Duration // Retry interval
}
RetryStrategy defines lock retry strategy
func (*RetryStrategy) Validate ¶
func (rs *RetryStrategy) Validate() error
Validate validates the retry strategy (MaxRetries and RetryDelay non-negative).