106 lines
2.7 KiB
Go
106 lines
2.7 KiB
Go
|
|
package redislock
|
||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"fmt"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
)
|
||
|
|
|
||
|
|
type fakeClient struct {
|
||
|
|
value string
|
||
|
|
ttlMS int64
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeClient) SetnxExCtx(ctx context.Context, key, value string, seconds int) (bool, error) {
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return false, err
|
||
|
|
}
|
||
|
|
if f.value != "" {
|
||
|
|
return false, nil
|
||
|
|
}
|
||
|
|
f.value = value
|
||
|
|
f.ttlMS = int64(seconds) * 1000
|
||
|
|
return true, nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func (f *fakeClient) EvalCtx(ctx context.Context, script string, keys []string, args ...any) (any, error) {
|
||
|
|
if err := ctx.Err(); err != nil {
|
||
|
|
return nil, err
|
||
|
|
}
|
||
|
|
owner := fmt.Sprint(args[0])
|
||
|
|
if f.value != owner {
|
||
|
|
return int64(0), nil
|
||
|
|
}
|
||
|
|
if script == renewScript {
|
||
|
|
f.ttlMS = args[1].(int64)
|
||
|
|
return int64(1), nil
|
||
|
|
}
|
||
|
|
if script == releaseScript {
|
||
|
|
f.value = ""
|
||
|
|
return int64(1), nil
|
||
|
|
}
|
||
|
|
return nil, fmt.Errorf("unexpected script")
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLockUsesUniqueTokenPerAcquisition(t *testing.T) {
|
||
|
|
client := &fakeClient{}
|
||
|
|
lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)
|
||
|
|
|
||
|
|
acquired, err := lock.Acquire(context.Background())
|
||
|
|
if err != nil || !acquired {
|
||
|
|
t.Fatalf("Acquire() = %v, %v", acquired, err)
|
||
|
|
}
|
||
|
|
first := client.value
|
||
|
|
if !strings.HasPrefix(first, "worker-7:") {
|
||
|
|
t.Fatalf("token %q has no worker prefix", first)
|
||
|
|
}
|
||
|
|
if err := lock.Release(context.Background()); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
acquired, err = lock.Acquire(context.Background())
|
||
|
|
if err != nil || !acquired {
|
||
|
|
t.Fatalf("second Acquire() = %v, %v", acquired, err)
|
||
|
|
}
|
||
|
|
if client.value == first {
|
||
|
|
t.Fatal("successful acquisitions reused a token")
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestRenewAndReleaseRequireCurrentOwnership(t *testing.T) {
|
||
|
|
client := &fakeClient{}
|
||
|
|
lock := NewWithClient(client, "locks:outbox", "worker-7", 3*time.Minute)
|
||
|
|
if acquired, err := lock.Acquire(context.Background()); err != nil || !acquired {
|
||
|
|
t.Fatalf("Acquire() = %v, %v", acquired, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
client.ttlMS = 1
|
||
|
|
renewed, err := lock.Renew(context.Background())
|
||
|
|
if err != nil || !renewed || client.ttlMS != int64(3*time.Minute/time.Millisecond) {
|
||
|
|
t.Fatalf("Renew() = %v, %v, ttl=%d", renewed, err, client.ttlMS)
|
||
|
|
}
|
||
|
|
|
||
|
|
client.value = "worker-8:successor"
|
||
|
|
renewed, err = lock.Renew(context.Background())
|
||
|
|
if err != nil || renewed {
|
||
|
|
t.Fatalf("lost-owner Renew() = %v, %v", renewed, err)
|
||
|
|
}
|
||
|
|
if err := lock.Release(context.Background()); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if client.value != "worker-8:successor" {
|
||
|
|
t.Fatalf("Release removed successor lease: %q", client.value)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestLockMethodsHonorCanceledContext(t *testing.T) {
|
||
|
|
client := &fakeClient{}
|
||
|
|
lock := NewWithClient(client, "locks:outbox", "worker-7", time.Minute)
|
||
|
|
ctx, cancel := context.WithCancel(context.Background())
|
||
|
|
cancel()
|
||
|
|
if _, err := lock.Acquire(ctx); err == nil {
|
||
|
|
t.Fatal("Acquire succeeded with canceled context")
|
||
|
|
}
|
||
|
|
}
|