@@ -15,10 +15,9 @@ package redis
15
15
16
16
import (
17
17
"context"
18
+ "errors"
18
19
"fmt"
19
20
"reflect"
20
- "strconv"
21
- "strings"
22
21
"time"
23
22
24
23
rediscomponent "github.com/dapr/components-contrib/internal/component/redis"
@@ -27,13 +26,10 @@ import (
27
26
"github.com/dapr/kit/logger"
28
27
)
29
28
30
- const (
31
- unlockScript = "local v = redis.call(\" get\" ,KEYS[1]); if v==false then return -1 end; if v~=ARGV[1] then return -2 else return redis.call(\" del\" ,KEYS[1]) end"
32
- connectedSlavesReplicas = "connected_slaves:"
33
- infoReplicationDelimiter = "\r \n "
34
- )
29
+ const unlockScript = `local v = redis.call("get",KEYS[1]); if v==false then return -1 end; if v~=ARGV[1] then return -2 else return redis.call("del",KEYS[1]) end`
35
30
36
- // Standalone Redis lock store.Any fail-over related features are not supported,such as Sentinel and Redis Cluster.
31
+ // Standalone Redis lock store.
32
+ // Any fail-over related features are not supported, such as Sentinel and Redis Cluster.
37
33
type StandaloneRedisLock struct {
38
34
client rediscomponent.RedisClient
39
35
clientSettings * rediscomponent.Settings
@@ -52,83 +48,46 @@ func NewStandaloneRedisLock(logger logger.Logger) lock.Store {
52
48
}
53
49
54
50
// Init StandaloneRedisLock.
55
- func (r * StandaloneRedisLock ) InitLockStore (ctx context.Context , metadata lock.Metadata ) error {
56
- // must have `redisHost`
57
- if metadata .Properties ["redisHost" ] == "" {
58
- return fmt .Errorf ("[standaloneRedisLock]: InitLockStore error. redisHost is empty" )
59
- }
60
- // no failover
61
- if needFailover (metadata .Properties ) {
62
- return fmt .Errorf ("[standaloneRedisLock]: InitLockStore error. Failover is not supported" )
63
- }
64
- // construct client
65
- var err error
51
+ func (r * StandaloneRedisLock ) InitLockStore (ctx context.Context , metadata lock.Metadata ) (err error ) {
52
+ // Create the client
66
53
r .client , r .clientSettings , err = rediscomponent .ParseClientFromProperties (metadata .Properties , contribMetadata .LockStoreType )
67
54
if err != nil {
68
55
return err
69
56
}
70
- // 3. connect to redis
71
- if _ , err = r .client .PingResult (ctx ); err != nil {
72
- return fmt .Errorf ("[standaloneRedisLock]: error connecting to redis at %s: %s" , r .clientSettings .Host , err )
73
- }
74
- // no replica
75
- replicas , err := r .getConnectedSlaves (ctx )
76
- // pass the validation if error occurs,
77
- // since some redis versions such as miniredis do not recognize the `INFO` command.
78
- if err == nil && replicas > 0 {
79
- return fmt .Errorf ("[standaloneRedisLock]: InitLockStore error. Replication is not supported" )
80
- }
81
- return nil
82
- }
83
57
84
- func needFailover (properties map [string ]string ) bool {
85
- if val , ok := properties ["failover" ]; ok && val != "" {
86
- parsedVal , err := strconv .ParseBool (val )
87
- if err != nil {
88
- return false
89
- }
90
- return parsedVal
58
+ // Ensure we have a host
59
+ if r .clientSettings .Host == "" {
60
+ return errors .New ("metadata property redisHost is empty" )
91
61
}
92
- return false
93
- }
94
62
95
- func (r * StandaloneRedisLock ) getConnectedSlaves (ctx context.Context ) (int , error ) {
96
- res , err := r .client .DoRead (ctx , "INFO" , "replication" )
97
- if err != nil {
98
- return 0 , err
63
+ // We do not support failover or having replicas
64
+ if r .clientSettings .Failover {
65
+ return errors .New ("this component does not support connecting to Redis with failover" )
99
66
}
100
67
101
- // Response example: https://redis.io/commands/info#return-value
102
- // # Replication\r\nrole:master\r\nconnected_slaves:1\r\n
103
- s , _ := strconv .Unquote (fmt .Sprintf ("%q" , res ))
104
- if len (s ) == 0 {
105
- return 0 , nil
68
+ // Ping Redis to ensure the connection is uo
69
+ if _ , err = r .client .PingResult (ctx ); err != nil {
70
+ return fmt .Errorf ("error connecting to Redis: %v" , err )
106
71
}
107
72
108
- return r .parseConnectedSlaves (s ), nil
109
- }
110
-
111
- func (r * StandaloneRedisLock ) parseConnectedSlaves (res string ) int {
112
- infos := strings .Split (res , infoReplicationDelimiter )
113
- for _ , info := range infos {
114
- if strings .Contains (info , connectedSlavesReplicas ) {
115
- parsedReplicas , _ := strconv .ParseUint (info [len (connectedSlavesReplicas ):], 10 , 32 )
116
-
117
- return int (parsedReplicas )
118
- }
73
+ // Ensure there are no replicas
74
+ // Pass the validation if error occurs, since some Redis versions such as miniredis do not recognize the `INFO` command.
75
+ replicas , err := rediscomponent .GetConnectedSlaves (ctx , r .client )
76
+ if err == nil && replicas > 0 {
77
+ return errors .New ("replication is not supported" )
119
78
}
120
-
121
- return 0
79
+ return nil
122
80
}
123
81
124
- // Try to acquire a redis lock.
82
+ // TryLock tries to acquire a lock.
83
+ // If the lock cannot be acquired, it returns immediately.
125
84
func (r * StandaloneRedisLock ) TryLock (ctx context.Context , req * lock.TryLockRequest ) (* lock.TryLockResponse , error ) {
126
- // 1.Setting redis expiration time
85
+ // Set a key if doesn't exist with an expiration time
127
86
nxval , err := r .client .SetNX (ctx , req .ResourceID , req .LockOwner , time .Second * time .Duration (req .ExpiryInSeconds ))
128
87
if nxval == nil {
129
- return & lock.TryLockResponse {}, fmt .Errorf ("[standaloneRedisLock]: SetNX returned nil.ResourceID: %s" , req . ResourceID )
88
+ return & lock.TryLockResponse {}, fmt .Errorf ("setNX returned a nil response" )
130
89
}
131
- // 2. check error
90
+
132
91
if err != nil {
133
92
return & lock.TryLockResponse {}, err
134
93
}
@@ -138,46 +97,46 @@ func (r *StandaloneRedisLock) TryLock(ctx context.Context, req *lock.TryLockRequ
138
97
}, nil
139
98
}
140
99
141
- // Try to release a redis lock.
100
+ // Unlock tries to release a lock if the lock is still valid .
142
101
func (r * StandaloneRedisLock ) Unlock (ctx context.Context , req * lock.UnlockRequest ) (* lock.UnlockResponse , error ) {
143
- // 1. delegate to client.eval lua script
102
+ // Delegate to client.eval lua script
144
103
evalInt , parseErr , err := r .client .EvalInt (ctx , unlockScript , []string {req .ResourceID }, req .LockOwner )
145
- // 2. check error
146
104
if evalInt == nil {
147
- return newInternalErrorUnlockResponse (), fmt .Errorf ("[standaloneRedisLock]: Eval unlock script returned nil.ResourceID: %s" , req .ResourceID )
105
+ res := & lock.UnlockResponse {
106
+ Status : lock .InternalError ,
107
+ }
108
+ return res , errors .New ("eval unlock script returned a nil response" )
148
109
}
149
- // 3. parse result
150
- i := * evalInt
151
- status := lock .InternalError
110
+
111
+ // Parse result
152
112
if parseErr != nil {
153
113
return & lock.UnlockResponse {
154
- Status : status ,
114
+ Status : lock . InternalError ,
155
115
}, err
156
116
}
157
- if i >= 0 {
117
+ var status lock.Status
118
+ switch {
119
+ case * evalInt >= 0 :
158
120
status = lock .Success
159
- } else if i == - 1 {
121
+ case * evalInt == - 1 :
160
122
status = lock .LockDoesNotExist
161
- } else if i == - 2 {
123
+ case * evalInt == - 2 :
162
124
status = lock .LockBelongsToOthers
125
+ default :
126
+ status = lock .InternalError
163
127
}
128
+
164
129
return & lock.UnlockResponse {
165
130
Status : status ,
166
131
}, nil
167
132
}
168
133
169
- func newInternalErrorUnlockResponse () * lock.UnlockResponse {
170
- return & lock.UnlockResponse {
171
- Status : lock .InternalError ,
172
- }
173
- }
174
-
175
134
// Close shuts down the client's redis connections.
176
135
func (r * StandaloneRedisLock ) Close () error {
177
136
if r .client != nil {
178
- closeErr := r .client .Close ()
137
+ err := r .client .Close ()
179
138
r .client = nil
180
- return closeErr
139
+ return err
181
140
}
182
141
return nil
183
142
}
0 commit comments