-
Notifications
You must be signed in to change notification settings - Fork 1
/
lock.go
114 lines (109 loc) · 2.75 KB
/
lock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package red
import (
"context"
xerr "github.com/goclub/error"
"github.com/google/uuid"
"github.com/pkg/errors"
"strconv"
"time"
)
type Mutex struct {
Key string
Expire time.Duration
Retry Retry
startTime time.Time
lockValue string
client Connecter
}
func AsErrUnlock(err error) (unlockErr *ErrUnlock, asErrUnlock bool) {
asErrUnlock = errors.As(err, &unlockErr)
return
}
type ErrUnlock struct {
IsTimeout bool
IsUnexpectedError bool
IsConnectErr bool
Err error
}
// 自定义错误的 Error 方法一定要加 (*Errxxx) 原因:https://github.com/goclub/error
func (e *ErrUnlock) Error() string {
return e.Err.Error()
}
func (e *ErrUnlock) Unwrap() error {
return e.Err
}
func (data *Mutex) unlock (ctx context.Context) (err error) {
// if data.startTime.After(time.Now().Add(data.Expire)) {
// return &ErrUnlock{
// IsTimeout: true,
// Err: errors.New("goclub/redis: IsTimeout Mutex{}.Unlock() key:" + data.Key + " is timeout"),
// }
// }
var delCount int64
script := `
if redis.call("get", KEYS[1]) == ARGV[1]
then
return redis.call("del", KEYS[1])
else
return 0
end
`
reply, err := data.client.EvalWithoutNil(ctx, Script{
KEYS: []string{data.Key},
ARGV: []string{data.lockValue},
Script: script,
}) ; if err != nil {
return xerr.WithStack(&ErrUnlock{
IsConnectErr: true,
Err: err,
})
}
delCount, err = reply.Int64() ; if err != nil {
return
}
switch delCount {
case 0:
return xerr.WithStack(&ErrUnlock{
IsTimeout: true,
Err: errors.New("goclub/redis: IsTimeout Mutex{}.Unlock() key:" + data.Key + " is timeout"),
})
case 1:
return nil
default:
return xerr.WithStack(&ErrUnlock{
IsUnexpectedError: true,
Err: errors.New("goclub/redis: IsUnexpectedError Mutex{}.Unlock() del " + data.Key + " count:" + strconv.Itoa(int(delCount))),
})
}
}
func (data *Mutex) Lock(ctx context.Context, client Connecter) ( ok bool, unlock func(ctx context.Context) (err error), err error) {
if data.Key == "" { err = xerr.New("goclub/redis: key can not be empty string") ; return}
err = data.Retry.check() ; if err != nil {
return
}
retryCount := int(data.Retry.Times)
ok, err = mutexLock(ctx, client, data, &retryCount)
unlock = data.unlock
return
}
func mutexLock(ctx context.Context, client Connecter, data *Mutex, retryCount *int) (ok bool, err error) {
data.startTime = time.Now() // start time 必须在 SETNX 之前记录,否则会在SETNX 延迟时候导致时间错误
data.client = client
data.lockValue = uuid.NewString()
_, isNil, err := SET{
Key: data.Key,
Value: data.lockValue,
Expire: data.Expire,
NX: true,
}.Do(ctx, client)
if isNil {
*retryCount--
if *retryCount == -1 {
return
}
time.Sleep(data.Retry.Interval)
return mutexLock(ctx, client, data, retryCount)
}
ok = true
return
}