Skip to content

Commit

Permalink
Optimize the naming of reentrant locks (#29)
Browse files Browse the repository at this point in the history
* Optimize the naming of reentrant locks

* test fix
  • Loading branch information
jefferyjob committed Aug 9, 2024
1 parent 41eafa6 commit 20954ec
Show file tree
Hide file tree
Showing 10 changed files with 113 additions and 70 deletions.
25 changes: 24 additions & 1 deletion CHANGELOG.cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,27 @@
- 支持自定义超时时间和自动续期,根据实际需求进行灵活配置

## v1.0.1
- 修复包名问题
- 修复包名问题

## v1.0.2
-`v1.0.0` 标记废弃 #15
-`codecov/codecov-action` 升级到版本4 #11

## v1.0.3
- 优化Lua脚本 #16

## v1.1.0
- 兼容新版本`redis/go-redis` #17
- 错误统一定义 #18
- 删除未使用的选项方法 #19
- 调整自动续订时间 #20
-`github.com/redis/go-redis/v9``9.5.4` 升级到 `9.6.1` #23

## v1.1.1
- 单元测试覆盖与错误优化 #25
- 错误修复:在并发情况下,token相似会导致多次获取锁 #26

## v1.1.2
- Dependabot 计划间隔每周 #27
- 删除毫无意义的 `sync.Mutex` #28
- 优化可重入锁的命名 #29
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,27 @@
- Support custom timeout and automatic renewal, flexible configuration according to actual needs

## v1.0.1
- Fix package name issue
- Fix package name issue

## v1.0.2
- Mark `v1.0.0` as deprecated #15
- Upgrade `codecov/codecov-action` to version 4 #11

## v1.0.3
- Optimize Lua scripts #16

## v1.1.0
- Compatible with new version `redis/go-redis` #17
- Unify error definitions #18
- Delete unused option methods #19
- Adjust auto-renewal time #20
- Upgrade `github.com/redis/go-redis/v9` from `9.5.4` to `9.6.1` #23

## v1.1.1
- Unit test coverage and error optimization #25
- Fix: In concurrent situations, similar tokens will cause multiple lock acquisitions #26

## v1.1.2
- Dependabot scheduled every week #27
- Delete meaningless `sync.Mutex` #28
- Optimize the naming of reentrant locks #29
15 changes: 3 additions & 12 deletions lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,16 @@ import (
"fmt"
"github.com/google/uuid"
"github.com/redis/go-redis/v9"
"sync"
"time"
)

// 默认锁超时时间
const lockTime = 5 * time.Second

type RedisLockInter interface {
// Lock 加锁
Lock() error
// UnLock 解锁
UnLock() error
// SpinLock 自旋锁
SpinLock(timeout time.Duration) error
// UnLock 解锁
UnLock() error
// Renew 手动续期
Renew() error
}
Expand All @@ -37,13 +33,11 @@ type RedisLock struct {
isAutoRenew bool
autoRenewCtx context.Context
autoRenewCancel context.CancelFunc
mutex sync.Mutex
}

type Option func(lock *RedisLock)

func New(ctx context.Context, redisClient RedisInter, lockKey string, options ...Option) RedisLockInter {

lock := &RedisLock{
Context: ctx,
redis: redisClient,
Expand All @@ -52,14 +46,11 @@ func New(ctx context.Context, redisClient RedisInter, lockKey string, options ..
for _, f := range options {
f(lock)
}

lock.key = lockKey

// token 自动生成
// automatically generate tokens
if lock.token == "" {
lock.token = fmt.Sprintf("lock_token:%s", uuid.New().String())
}

return lock
}

Expand Down
20 changes: 9 additions & 11 deletions lock_redis.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,17 @@ import (
)

var (
//go:embed lua/lock.lua
lockScript string
//go:embed lua/unLock.lua
unLockScript string
//go:embed lua/renew.lua
renewScript string
//go:embed lua/reentrantLock.lua
reentrantLockScript string
//go:embed lua/reentrantUnLock.lua
reentrantUnLockScript string
//go:embed lua/reentrantRenew.lua
reentrantRenewScript string
)

// Lock 加锁
func (lock *RedisLock) Lock() error {
result, err := lock.redis.Eval(lock.Context, lockScript, []string{lock.key}, lock.token, lock.lockTimeout.Seconds()).Result()
result, err := lock.redis.Eval(lock.Context, reentrantLockScript, []string{lock.key}, lock.token, lock.lockTimeout.Seconds()).Result()

if err != nil {
return ErrException
Expand All @@ -42,12 +42,11 @@ func (lock *RedisLock) UnLock() error {
lock.autoRenewCancel()
}

result, err := lock.redis.Eval(lock.Context, unLockScript, []string{lock.key}, lock.token).Result()
result, err := lock.redis.Eval(lock.Context, reentrantUnLockScript, []string{lock.key}, lock.token).Result()

if err != nil {
return ErrException
}

if result != "OK" {
return ErrUnLockFailed
}
Expand Down Expand Up @@ -80,12 +79,11 @@ func (lock *RedisLock) SpinLock(timeout time.Duration) error {

// Renew 锁手动续期
func (lock *RedisLock) Renew() error {
res, err := lock.redis.Eval(lock.Context, renewScript, []string{lock.key}, lock.token, lock.lockTimeout.Seconds()).Result()
res, err := lock.redis.Eval(lock.Context, reentrantRenewScript, []string{lock.key}, lock.token, lock.lockTimeout.Seconds()).Result()

if err != nil {
return ErrException
}

if res != "OK" {
return ErrLockRenewFailed
}
Expand Down
28 changes: 14 additions & 14 deletions lock_redis_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func TestLockSuccess(t *testing.T) {
lock := New(ctx, db, key, WithToken(token))

// 设置模拟锁获取成功的行为
mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")

err := lock.Lock()
if err != nil {
Expand All @@ -53,7 +53,7 @@ func TestLockFail(t *testing.T) {

ctx := context.Background()
db, mock := redismock.NewClientMock()
mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")

var wg sync.WaitGroup
wg.Add(2)
Expand Down Expand Up @@ -111,7 +111,7 @@ func TestUnlockFail(t *testing.T) {
lock := New(ctx, db, key, WithToken(token))

// 加锁逻辑
mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")

err := lock.Lock()
if err != nil {
Expand All @@ -120,7 +120,7 @@ func TestUnlockFail(t *testing.T) {
}

// 解锁逻辑
mock.ExpectEval(unLockScript, []string{key}, token+"test-2").SetVal(nil) // 模拟解锁失败
mock.ExpectEval(reentrantUnLockScript, []string{key}, token+"test-2").SetVal(nil) // 模拟解锁失败

err = lock.UnLock()

Expand All @@ -142,8 +142,8 @@ func TestSpinLockSuccess(t *testing.T) {
token2 := "some_token2"
spinTimeout := time.Duration(5) * time.Second

mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(lockScript, []string{key}, token2, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token2, lockTime.Seconds()).SetVal("OK")

var wg sync.WaitGroup
wg.Add(2)
Expand Down Expand Up @@ -192,8 +192,8 @@ func TestSpinLockTimeout(t *testing.T) {
spinTimeout := time.Duration(5) * time.Second
spinTimeout2 := time.Duration(3) * time.Second

mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(lockScript, []string{key}, token2, lockTime.Seconds()).SetVal("nil")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token2, lockTime.Seconds()).SetVal("nil")

var wg sync.WaitGroup
wg.Add(2)
Expand Down Expand Up @@ -238,8 +238,8 @@ func TestRenewSuccess(t *testing.T) {
key := "test_key_TestRenewSuccess"
token := "some_token"

mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(renewScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantRenewScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")

// 设置模拟锁续期成功的行为
mock.ExpectExpire(key, lockTime).SetVal(true)
Expand Down Expand Up @@ -283,7 +283,7 @@ func TestRenewFail(t *testing.T) {
key := "test_key_TestRenewFail"
token := "some_token"

mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
// 设置模拟锁续期成功的行为
mock.ExpectExpire(key, lockTime).SetVal(false)

Expand Down Expand Up @@ -328,7 +328,7 @@ func TestWithTimeout(t *testing.T) {
token := "some_token"
timeout := time.Duration(10) * time.Second

mock.ExpectEval(lockScript, []string{key}, token, timeout.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, timeout.Seconds()).SetVal("OK")

var wg sync.WaitGroup
wg.Add(2)
Expand Down Expand Up @@ -375,8 +375,8 @@ func TestAutoRenew(t *testing.T) {
key := "test_key_TestAutoRenew"
token := "some_token"

mock.ExpectEval(lockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(renewScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantLockScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectEval(reentrantRenewScript, []string{key}, token, lockTime.Seconds()).SetVal("OK")
mock.ExpectExpire(key, lockTime).SetVal(true)

lock := New(ctx, db, key, WithToken(token), WithAutoRenew())
Expand Down
Loading

0 comments on commit 20954ec

Please sign in to comment.