Skip to content

Commit 20703f6

Browse files
authored
refactor(locker): deprecate v1 change to v2 add unit tests (#332)
* test(locker): add unit tests for locker and owner * test(locker): add unit tests for locker and owner * feat(locker): deprecate v1 change to v2 * feat(locker): deprecate v1 change to v2 * feat(locker): deprecate v1 change to v2
1 parent 9804d20 commit 20703f6

14 files changed

+360
-547
lines changed

cache/redis/store.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -161,5 +161,8 @@ func (s *Store) Add(ctx context.Context, key string, value any, ttl time.Duratio
161161
}
162162

163163
func (s *Store) Lock(key string, ttl time.Duration) locker.Locker {
164-
return redisLocker.NewLocker(s.redis, s.opts.prefix+key, ttl)
164+
return redisLocker.NewLocker(s.redis,
165+
redisLocker.WithName(s.opts.prefix+key),
166+
redisLocker.WithTTL(ttl),
167+
)
165168
}

cache/redis/store_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -123,9 +123,8 @@ func TestRedis_Lock(t *testing.T) {
123123
wg.Add(1)
124124
go func() {
125125
defer wg.Done()
126-
err := r.Lock("test", 5*time.Second).Try(context.Background(), func() error {
126+
err := r.Lock("test", 5*time.Second).Try(context.Background(), func() {
127127
time.Sleep(time.Second)
128-
return nil
129128
})
130129
if err != nil {
131130
assert.True(t, errors.Is(err, locker.ErrLocked))

locker/README.md

+16-4
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,30 @@ import (
1111

1212
"github.com/redis/go-redis/v9"
1313

14+
"github.com/go-kratos-ecosystem/components/v2/cache"
15+
redisStore "github.com/go-kratos-ecosystem/components/v2/cache/redis"
1416
redisLocker "github.com/go-kratos-ecosystem/components/v2/locker/redis"
1517
)
1618

1719
func main() {
1820
client := redis.NewClient(&redis.Options{
1921
Addr: "localhost:6379",
20-
})
22+
})
23+
24+
// ex1: 直接用
25+
locker := redisLocker.NewLocker(client, redisLocker.WithName("lock"), redisLocker.WithTTL(5*time.Minute))
26+
_ = locker.Try(context.Background(), func() {
27+
// do something
28+
})
29+
30+
// ex2: 基于缓存用
31+
repository := cache.NewRepository(
32+
redisStore.New(client, redisStore.Prefix("cache")),
33+
)
2134

22-
locker := redisLocker.NewLocker(client, "lock", 5*time.Minute)
23-
_ = locker.Try(context.Background(), func() error {
35+
_ = repository.Lock("lock", 5*time.Minute).Try(context.Background(), func() {
2436
// do something
25-
return nil
2637
})
2738
}
39+
2840
```

locker/locker.go

+33-28
Original file line numberDiff line numberDiff line change
@@ -7,37 +7,42 @@ import (
77
)
88

99
var (
10-
ErrLocked = errors.New("locker: locked")
11-
ErrTimeout = errors.New("locker: timeout")
12-
ErrNotLocked = errors.New("locker: not locked")
10+
ErrLocked = errors.New("locker: the locker is locked")
11+
ErrTimeout = errors.New("locker: the locker is timeout")
12+
ErrNotLocked = errors.New("locker: the locker is not locked")
1313
)
1414

1515
type Locker interface {
16-
// Try tries to acquire the lock and execute the function.
17-
// If the lock is already acquired, it will return ErrLocked.
18-
// Otherwise, it will release the lock after the function is executed, and return the error of the function.
19-
Try(ctx context.Context, fn func() error) error
20-
21-
// Until tries to acquire the lock and execute the function.
22-
// If the lock is already acquired, it will wait until the lock is released or the timeout is reached.
23-
// If the timeout is reached, it will return ErrTimeout.
24-
// Otherwise, it will release the lock after the function is executed, and return the error of the function.
25-
Until(ctx context.Context, timeout time.Duration, fn func() error) error
26-
27-
// Release releases the lock for the current owner.
28-
// It returns true if the lock is released successfully, otherwise it returns false.
29-
// If the lock acquired by another owner, it will return false.
30-
Release(ctx context.Context) (bool, error)
31-
32-
// ForceRelease releases the lock forcefully.
33-
ForceRelease(ctx context.Context) error
16+
// Try tries to get the lock.
17+
// if the lock is locked, it will return an error.
18+
Try(ctx context.Context, fn func()) error
19+
20+
// Until tries to get the lock until the timeout.
21+
// if the lock is locked, it will wait until the lock is released or the timeout is reached.
22+
Until(ctx context.Context, timeout time.Duration, fn func()) error
23+
24+
// Get tries to get the lock.
25+
// if the lock is locked, it will return the owner of the lock or an error.
26+
Get(ctx context.Context) (Owner, error)
27+
28+
// Release releases the locked owner.
29+
// if the owner is not the locked owner, it will return an error.
30+
Release(ctx context.Context, owner Owner) error
3431

35-
// Owner return the owner of the lock.
36-
Owner() string
32+
// ForceRelease releases the lock forcibly.
33+
ForceRelease(ctx context.Context) error
3734

38-
// LockedOwner returns the current owner of the lock.
39-
// If the lock is not acquired, it will return an empty string and ErrNotLocked.
40-
// Unlike Owner, which returns the owner of the current node in the cluster,
41-
// LockedOwner returns the owner of the lock acquired by the Cluster.
42-
LockedOwner(ctx context.Context) (string, error)
35+
// LockedOwner returns the owner of the lock.
36+
LockedOwner(ctx context.Context) (Owner, error)
4337
}
38+
39+
type NoopLocker struct{}
40+
41+
var _ Locker = NoopLocker{}
42+
43+
func (l NoopLocker) Try(context.Context, func()) error { return nil }
44+
func (l NoopLocker) Until(context.Context, time.Duration, func()) error { return nil }
45+
func (l NoopLocker) Get(context.Context) (Owner, error) { return nil, nil }
46+
func (l NoopLocker) Release(context.Context, Owner) error { return nil }
47+
func (l NoopLocker) ForceRelease(context.Context) error { return nil }
48+
func (l NoopLocker) LockedOwner(context.Context) (Owner, error) { return nil, nil }

locker/locker_test.go

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package locker
2+
3+
import (
4+
"context"
5+
"testing"
6+
"time"
7+
8+
"github.com/stretchr/testify/assert"
9+
)
10+
11+
func TestLocker_NoopLocker(t *testing.T) {
12+
l := NoopLocker{}
13+
ctx := context.Background()
14+
15+
assert.NoError(t, l.Try(ctx, func() {}))
16+
assert.NoError(t, l.Until(ctx, time.Second, func() {}))
17+
18+
o, err := l.Get(ctx)
19+
assert.NoError(t, err)
20+
assert.Nil(t, o)
21+
22+
assert.NoError(t, l.Release(ctx, nil))
23+
assert.NoError(t, l.ForceRelease(ctx))
24+
25+
o, err = l.LockedOwner(ctx)
26+
assert.NoError(t, err)
27+
assert.Nil(t, o)
28+
}

locker/memory/locker.go

-114
This file was deleted.

locker/memory/locker_test.go

-37
This file was deleted.

locker/owner.go

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package locker
2+
3+
import (
4+
"context"
5+
6+
"github.com/google/uuid"
7+
)
8+
9+
type Owner interface {
10+
// Name returns the name of the owner.
11+
Name() string
12+
13+
// Release releases the lock.
14+
Release(ctx context.Context) error
15+
}
16+
17+
type owner struct {
18+
name string
19+
locker Locker
20+
}
21+
22+
type OwnerOption func(*owner)
23+
24+
func WithOwnerName(name string) OwnerOption {
25+
return func(o *owner) {
26+
o.name = name
27+
}
28+
}
29+
30+
func NewOwner(locker Locker, opts ...OwnerOption) Owner {
31+
o := &owner{
32+
name: uuid.New().String(),
33+
locker: locker,
34+
}
35+
for _, opt := range opts {
36+
opt(o)
37+
}
38+
return o
39+
}
40+
41+
func (o *owner) Name() string {
42+
return o.name
43+
}
44+
45+
func (o *owner) Release(ctx context.Context) error {
46+
return o.locker.Release(ctx, o)
47+
}

locker/owner_test.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package locker
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestOwner_owner(t *testing.T) {
11+
o := NewOwner(NoopLocker{}, WithOwnerName("test"))
12+
assert.Equal(t, "test", o.Name())
13+
assert.NoError(t, o.Release(context.Background()))
14+
}

0 commit comments

Comments
 (0)