diff --git a/CHANGELOG.md b/CHANGELOG.md index 0eed4b3..ee62aee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## v0.9.3 + +- Feature: allow custom lock tokens [#66](https://github.com/bsm/redislock/pull/66) + ## v0.9.2 - Feature: better handling of nil lock.Release() [#68](https://github.com/bsm/redislock/pull/68) diff --git a/go.mod b/go.mod index 5c17ea6..5c906cd 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module github.com/bsm/redislock go 1.17 -require github.com/redis/go-redis/v9 v9.0.2 +require github.com/redis/go-redis/v9 v9.0.3 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 37b4eea..02c58f3 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,10 @@ -github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ= -github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= -github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8= -github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk= +github.com/bsm/ginkgo/v2 v2.7.0 h1:ItPMPH90RbmZJt5GtkcNvIRuGEdwlBItdNVoyzaNQao= +github.com/bsm/ginkgo/v2 v2.7.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w= +github.com/bsm/gomega v1.26.0 h1:LhQm+AFcgV2M0WyKroMASzAzCAJVpAxQXv4SaI9a69Y= +github.com/bsm/gomega v1.26.0/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE= -github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/redis/go-redis/v9 v9.0.3 h1:+7mmR26M0IvyLxGZUHxu4GiBkJkVDid0Un+j4ScYu4k= +github.com/redis/go-redis/v9 v9.0.3/go.mod h1:WqMKv5vnQbRuZstUwxQI195wHy+t4PuXDOjzMvcuQHk= diff --git a/redislock.go b/redislock.go index ac05b76..158d954 100644 --- a/redislock.go +++ b/redislock.go @@ -49,10 +49,14 @@ func New(client RedisClient) *Client { // Obtain tries to obtain a new lock using a key with the given TTL. // May return ErrNotObtained if not successful. func (c *Client) Obtain(ctx context.Context, key string, ttl time.Duration, opt *Options) (*Lock, error) { + token := opt.getToken() + // Create a random token - token, err := c.randomToken() - if err != nil { - return nil, err + if token == "" { + var err error + if token, err = c.randomToken(); err != nil { + return nil, err + } } value := token + opt.getMetadata() @@ -71,7 +75,7 @@ func (c *Client) Obtain(ctx context.Context, key string, ttl time.Duration, opt if err != nil { return nil, err } else if ok { - return &Lock{Client: c, key: key, value: value}, nil + return &Lock{Client: c, key: key, value: value, tokenLen: len(token)}, nil } backoff := retry.NextBackoff() @@ -117,8 +121,9 @@ func (c *Client) randomToken() (string, error) { // Lock represents an obtained, distributed lock. type Lock struct { *Client - key string - value string + key string + value string + tokenLen int } // Obtain is a short-cut for New(...).Obtain(...). @@ -133,12 +138,12 @@ func (l *Lock) Key() string { // Token returns the token value set by the lock. func (l *Lock) Token() string { - return l.value[:22] + return l.value[:l.tokenLen] } // Metadata returns the metadata of the lock. func (l *Lock) Metadata() string { - return l.value[22:] + return l.value[l.tokenLen:] } // TTL returns the remaining time-to-live. Returns 0 if the lock has expired. @@ -197,8 +202,12 @@ type Options struct { // Default: do not retry RetryStrategy RetryStrategy - // Metadata string is appended to the lock token. + // Metadata string. Metadata string + + // Token is a unique value that is used to identify the lock. By default, a random tokens are generated. Use this + // option to provide a custom token instead. + Token string } func (o *Options) getMetadata() string { @@ -208,6 +217,13 @@ func (o *Options) getMetadata() string { return "" } +func (o *Options) getToken() string { + if o != nil { + return o.Token + } + return "" +} + func (o *Options) getRetryStrategy() RetryStrategy { if o != nil && o.RetryStrategy != nil { return o.RetryStrategy diff --git a/redislock_test.go b/redislock_test.go index 08590dc..455e045 100644 --- a/redislock_test.go +++ b/redislock_test.go @@ -90,6 +90,25 @@ func TestObtain_metadata(t *testing.T) { } } +func TestObtain_custom_token(t *testing.T) { + ctx := context.Background() + rc := redis.NewClient(redisOpts) + defer teardown(t, rc) + + lock, err := Obtain(ctx, rc, lockKey, time.Hour, &Options{Token: "foo", Metadata: "bar"}) + if err != nil { + t.Fatal(err) + } + defer lock.Release(ctx) + + if exp, got := "foo", lock.Token(); exp != got { + t.Fatalf("expected %v, got %v", exp, got) + } + if exp, got := "bar", lock.Metadata(); exp != got { + t.Fatalf("expected %v, got %v", exp, got) + } +} + func TestObtain_retry_success(t *testing.T) { ctx := context.Background() rc := redis.NewClient(redisOpts)