Files
zeekayandHanzo Dev 1ad510e19d refactor: follow hanzokv/go v9.22.0 package rename redis -> kv
github.com/hanzokv/go/v9 v9.22.0 renamed its Go package from "redis" to
"kv"; the import path is unchanged. A lock library is typed against the
client it wraps -- the adapter's NewPool takes the client's
UniversalClient and compares against the client's Nil sentinel -- so the
qualifier change is load bearing, not cosmetic.

Client-driven:

- go.mod: github.com/hanzokv/go/v9 v9.21.1 -> v9.22.0
- the adapter for our client speaks kv.UniversalClient / kv.Nil, imported
  unaliased as in hanzokv/go's own sources

Our own vocabulary follows:

- redis/ -> kv/, package redis -> package kv. This is redsync's own
  Pool/Conn/Script abstraction over a KV backend, not anyone's product.
- redis/goredis/v9 -> kv/kvgo (package kvgo). That adapter wraps
  hanzokv/go, not go-redis; the old name said the wrong thing. Its
  example moves with it: examples/goredis/v9 -> examples/kvgo.
- import alias redsyncredis -> redsynckv
- RedisError -> KVError (and its tests)
- comments and README speak kv

Deliberately NOT renamed, because they name foreign things:

- the redigo, rueidis, valkeygo and go-redis v6/v7/v8 adapters, which
  target genuinely different clients, keep their names and their own
  "redis." qualifiers (redigo's redis.Conn, go-redis's redis.Nil)
- the server-side Lua in scripts.go: redis.call is the store's scripting
  API. Rewriting it would change the script source and therefore the
  SHA1 hashes pinned in scripts.go, which TestScripts_Hash verifies
  against a live server.
- upstream links (redis.io distlock post, redigo attribution)

Import paths move from .../v4/redis/... to .../v4/kv/...; the module
major stays v4 by policy.

Verified with a local redis-server: go build ./..., go vet ./...,
go test -count=1 ./... and go test -race -count=1 ./... all pass,
TestScripts_Hash included.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-26 08:53:51 -07:00

164 lines
4.3 KiB
Go

package redsync
import (
"math/rand"
"slices"
"time"
"github.com/hanzokv/lock/v4/kv"
)
const (
minRetryDelayMilliSec = 50
maxRetryDelayMilliSec = 250
)
// Redsync provides a simple method for creating distributed mutexes using multiple KV connection pools.
type Redsync struct {
pools []kv.Pool
}
// New creates and returns a new Redsync instance from given KV connection pools.
func New(pools ...kv.Pool) *Redsync {
return &Redsync{
pools: pools,
}
}
// NewMutex returns a new distributed mutex with given name.
func (r *Redsync) NewMutex(name string, options ...Option) *Mutex {
m := &Mutex{
name: name,
expiry: 8 * time.Second,
tries: 32,
delayFunc: func(tries int) time.Duration {
return time.Duration(rand.Intn(maxRetryDelayMilliSec-minRetryDelayMilliSec)+minRetryDelayMilliSec) * time.Millisecond
},
genValueFunc: genValue,
driftFactor: 0.01,
timeoutFactor: 0.05,
quorum: len(r.pools)/2 + 1,
pools: r.pools,
}
for _, o := range options {
o.Apply(m)
}
if m.shuffle {
// Create a copy to avoid data races between concurrent locks.
m.pools = slices.Clone(m.pools)
randomPools(m.pools)
}
return m
}
// An Option configures a mutex.
type Option interface {
Apply(*Mutex)
}
// OptionFunc is a function that configures a mutex.
type OptionFunc func(*Mutex)
// Apply calls f(mutex)
func (f OptionFunc) Apply(mutex *Mutex) {
f(mutex)
}
// WithExpiry can be used to set the expiry of a mutex to the given value.
// The default is 8s.
func WithExpiry(expiry time.Duration) Option {
return OptionFunc(func(m *Mutex) {
m.expiry = expiry
})
}
// WithTries can be used to set the number of times lock acquire is attempted.
// The default value is 32.
func WithTries(tries int) Option {
return OptionFunc(func(m *Mutex) {
m.tries = tries
})
}
// WithRetryDelay can be used to set the amount of time to wait between retries.
// The default value is rand(50ms, 250ms).
func WithRetryDelay(delay time.Duration) Option {
return OptionFunc(func(m *Mutex) {
m.delayFunc = func(tries int) time.Duration {
return delay
}
})
}
// WithSetNXOnExtend improves extending logic to extend the key if exist
// and if not, tries to set a new key in the KV store
// Useful if your KV nodes restart often and you want to reduce the chances of losing the lock
// Read this MR for more info: https://github.com/go-redsync/redsync/pull/149
func WithSetNXOnExtend() Option {
return OptionFunc(func(m *Mutex) {
m.setNXOnExtend = true
})
}
// WithRetryDelayFunc can be used to override default delay behavior.
func WithRetryDelayFunc(delayFunc DelayFunc) Option {
return OptionFunc(func(m *Mutex) {
m.delayFunc = delayFunc
})
}
// WithDriftFactor can be used to set the clock drift factor.
// The default value is 0.01.
func WithDriftFactor(factor float64) Option {
return OptionFunc(func(m *Mutex) {
m.driftFactor = factor
})
}
// WithTimeoutFactor can be used to set the timeout factor.
// The default value is 0.05.
func WithTimeoutFactor(factor float64) Option {
return OptionFunc(func(m *Mutex) {
m.timeoutFactor = factor
})
}
// WithGenValueFunc can be used to set the custom value generator.
func WithGenValueFunc(genValueFunc func() (string, error)) Option {
return OptionFunc(func(m *Mutex) {
m.genValueFunc = genValueFunc
})
}
// WithValue can be used to assign the random value without having to call lock.
// This allows the ownership of a lock to be "transferred" and allows the lock to be unlocked from elsewhere.
func WithValue(v string) Option {
return OptionFunc(func(m *Mutex) {
m.value = v
})
}
// WithFailFast can be used to quickly acquire and release the lock.
// When some KV servers are blocking, we do not need to wait for responses from all the KV servers response.
// As long as the quorum is met, we can assume the lock is acquired. The effect of this parameter is to achieve low
// latency, avoid KV blocking causing Lock/Unlock to not return for a long time.
func WithFailFast(b bool) Option {
return OptionFunc(func(m *Mutex) {
m.failFast = b
})
}
// WithShufflePools can be used to shuffle KV pools to reduce centralized access in concurrent scenarios.
func WithShufflePools(b bool) Option {
return OptionFunc(func(m *Mutex) {
m.shuffle = b
})
}
// randomPools shuffles KV pools.
func randomPools(pools []kv.Pool) {
rand.Shuffle(len(pools), func(i, j int) {
pools[i], pools[j] = pools[j], pools[i]
})
}