Skip to content

Commit f477aa0

Browse files
WanMuhafidzFaldiagungdwiprasetyo
authored andcommitted
Enhancement locker.go with options when want using prefix and time to live and add a function for isLockedTTL
1 parent b970fb8 commit f477aa0

File tree

2 files changed

+123
-29
lines changed

2 files changed

+123
-29
lines changed

candiutils/locker.go

Lines changed: 120 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package candiutils
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"time"
78

89
"github.com/gomodule/redigo/redis"
@@ -13,76 +14,161 @@ import (
1314
type (
1415
// RedisLocker lock using redis
1516
RedisLocker struct {
16-
pool *redis.Pool
17+
pool *redis.Pool
18+
lockeroptions LockerOptions
1719
}
1820

1921
// NoopLocker empty locker
2022
NoopLocker struct{}
23+
24+
// Options for RedisLocker
25+
LockerOptions struct {
26+
Prefix string
27+
TTL time.Duration
28+
}
29+
30+
// Option function type for setting options
31+
LockerOption func(*LockerOptions)
2132
)
2233

34+
// WithPrefix sets the prefix for keys
35+
func WithPrefixLocker(prefix string) LockerOption {
36+
return func(o *LockerOptions) {
37+
o.Prefix = prefix
38+
}
39+
}
40+
41+
// WithTTL sets the default TTL for keys
42+
func WithTTLLocker(ttl time.Duration) LockerOption {
43+
return func(o *LockerOptions) {
44+
o.TTL = ttl
45+
}
46+
}
47+
2348
// NewRedisLocker constructor
24-
func NewRedisLocker(pool *redis.Pool) *RedisLocker {
25-
return &RedisLocker{pool: pool}
49+
func NewRedisLocker(pool *redis.Pool, opts ...LockerOption) *RedisLocker {
50+
lockeroptions := LockerOptions{
51+
Prefix: "LOCKFOR",
52+
TTL: 0,
53+
}
54+
for _, opt := range opts {
55+
opt(&lockeroptions)
56+
}
57+
return &RedisLocker{pool: pool, lockeroptions: lockeroptions}
58+
}
59+
60+
// GetPrefix returns the prefix used for keys
61+
func (r *RedisLocker) GetPrefixLocker() string {
62+
return r.lockeroptions.Prefix + ":"
63+
}
64+
65+
// GetTTLLocker returns the default TTL for keys
66+
func (r *RedisLocker) GetTTLLocker() time.Duration {
67+
return r.lockeroptions.TTL
68+
}
69+
70+
func (r *RedisLocker) IsLocked(key string) bool {
71+
conn := r.pool.Get()
72+
defer conn.Close()
73+
74+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
75+
incr, err := redis.Int64(conn.Do("INCR", lockKey))
76+
if err != nil {
77+
return false
78+
}
79+
80+
return incr > 1
2681
}
2782

28-
func (r *RedisLocker) IsLocked(key string, expiration ...time.Duration) bool {
83+
func (r *RedisLocker) IsLockedTTL(key string, TTL time.Duration) bool {
2984
conn := r.pool.Get()
30-
incr, _ := redis.Int64(conn.Do("INCR", key))
85+
defer conn.Close()
86+
87+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
88+
incr, err := redis.Int64(conn.Do("INCR", lockKey))
89+
if err != nil {
90+
return false
91+
}
3192

32-
if len(expiration) > 0 {
33-
conn.Do("EXPIRE", key, int(expiration[0].Seconds()))
93+
var expireTime time.Duration
94+
if TTL > 0 {
95+
expireTime = TTL
96+
} else {
97+
expireTime = r.lockeroptions.TTL
98+
}
99+
100+
if expireTime > 0 {
101+
conn.Do("EXPIRE", lockKey, int(expireTime.Seconds()))
34102
}
35-
conn.Close()
36103

37104
return incr > 1
38105
}
39106

40107
func (r *RedisLocker) HasBeenLocked(key string) bool {
41108
conn := r.pool.Get()
42-
incr, _ := redis.Int64(conn.Do("GET", key))
43-
conn.Close()
109+
defer conn.Close()
110+
111+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
112+
incr, _ := redis.Int64(conn.Do("GET", lockKey))
44113

45114
return incr > 0
46115
}
47116

48117
// Unlock method
49118
func (r *RedisLocker) Unlock(key string) {
50119
conn := r.pool.Get()
51-
conn.Do("DEL", key)
52-
conn.Close()
120+
defer conn.Close()
121+
122+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
123+
conn.Do("DEL", lockKey)
53124
}
54125

55126
// Reset method
56127
func (r *RedisLocker) Reset(key string) {
57128
conn := r.pool.Get()
58-
keys, _ := redis.Strings(conn.Do("KEYS", key))
129+
defer conn.Close()
130+
131+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
132+
keys, err := redis.Strings(conn.Do("KEYS", lockKey))
133+
if err != nil {
134+
fmt.Println("Error when reset locker: ", key, err)
135+
return
136+
}
137+
59138
for _, k := range keys {
60-
conn.Do("DEL", k)
139+
_, err := conn.Do("DEL", k)
140+
if err != nil {
141+
fmt.Println("Error when reset locker: ", key, err)
142+
}
61143
}
62-
conn.Close()
63-
return
64144
}
65145

66146
// Disconnect close and reset
67147
func (r *RedisLocker) Disconnect(ctx context.Context) error {
68148
conn := r.pool.Get()
69-
conn.Do("DEL", "LOCKFOR:*")
70-
conn.Close()
149+
defer conn.Close()
150+
151+
lockKey := fmt.Sprintf("%s:*", r.lockeroptions.Prefix)
152+
_, err := conn.Do("DEL", lockKey)
153+
if err != nil {
154+
return err
155+
}
156+
71157
return nil
72158
}
73159

74160
// Lock method
75161
func (r *RedisLocker) Lock(key string, timeout time.Duration) (unlockFunc func(), err error) {
76162
if timeout <= 0 {
77-
return func() {}, errors.New("Timeout must be positive")
163+
return func() {}, errors.New("timeout must be positive")
78164
}
79165
if key == "" {
80-
return func() {}, errors.New("Key cannot empty")
166+
return func() {}, errors.New("key cannot empty")
81167
}
82168

83-
lockKey := "LOCKFOR:" + key
84-
unlockFunc = func() { r.Unlock(lockKey) }
85-
if !r.IsLocked(lockKey) {
169+
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
170+
unlockFunc = func() { r.Unlock(key) }
171+
if !r.IsLocked(key) {
86172
return unlockFunc, nil
87173
}
88174

@@ -99,7 +185,7 @@ func (r *RedisLocker) Lock(key string, timeout time.Duration) (unlockFunc func()
99185
for {
100186
switch msg := psc.Receive().(type) {
101187
case redis.Message:
102-
if msg.Pattern == eventChannel && lockKey == string(msg.Data) && !r.IsLocked(lockKey) {
188+
if msg.Pattern == eventChannel && lockKey == string(msg.Data) && !r.IsLocked(key) {
103189
wait <- nil
104190
return
105191
}
@@ -116,8 +202,8 @@ func (r *RedisLocker) Lock(key string, timeout time.Duration) (unlockFunc func()
116202
return unlockFunc, err
117203

118204
case <-time.After(timeout):
119-
r.Unlock(lockKey)
120-
return unlockFunc, errors.New("Timeout when waiting unlock another process")
205+
r.Unlock(key)
206+
return unlockFunc, errors.New("timeout when waiting unlock another process")
121207
}
122208
}
123209

@@ -126,8 +212,8 @@ func (r *RedisLocker) Lock(key string, timeout time.Duration) (unlockFunc func()
126212
// IsLocked method
127213
func (NoopLocker) IsLocked(string) bool { return false }
128214

129-
// IsLocked method
130-
func (NoopLocker) IsLockedWithTTL(string, time.Duration) bool { return false }
215+
// IsLockedTTL method
216+
func (NoopLocker) IsLockedTTL(string, time.Duration) bool { return false }
131217

132218
// HasBeenLocked method
133219
func (NoopLocker) HasBeenLocked(string) bool { return false }
@@ -142,3 +228,9 @@ func (NoopLocker) Reset(string) {}
142228
func (NoopLocker) Lock(string, time.Duration) (func(), error) { return func() {}, nil }
143229

144230
func (NoopLocker) Disconnect(context.Context) error { return nil }
231+
232+
// GetPrefix method
233+
func (NoopLocker) GetPrefixLocker() string { return "" }
234+
235+
// GetTTLLocker method
236+
func (NoopLocker) GetTTLLocker() time.Duration { return 0 }

codebase/interfaces/locker.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ type (
66
// Locker abstraction, lock concurrent process
77
Locker interface {
88
IsLocked(key string) bool
9-
IsLockedWithTTL(key string, timeout time.Duration) bool
9+
IsLockedTTL(key string, ttl time.Duration) bool
1010
HasBeenLocked(key string) bool
1111
Unlock(key string)
1212
Reset(key string)
1313
Lock(key string, timeout time.Duration) (unlockFunc func(), err error)
14+
GetPrefixLocker() string
15+
GetTTLLocker() time.Duration
1416
Closer
1517
}
1618
)

0 commit comments

Comments
 (0)