Skip to content

Commit 8294951

Browse files
authored
Feat: lua script support retryable; rueidislock set all lua scripts retryable by default (#926)
For #925 - For Lua add LuaOption for setting this lua script retryable. - add testcases to verify that `WithRetryable(true)` option make lua script commands Retryable - For all lua scripts in rueidislock mod, set retryable by default.
1 parent f1748da commit 8294951

File tree

3 files changed

+349
-29
lines changed

3 files changed

+349
-29
lines changed

lua.go

Lines changed: 45 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -27,35 +27,49 @@ func WithLoadSHA1(enabled bool) LuaOption {
2727
// NewLuaScript creates a Lua instance whose Lua.Exec uses EVALSHA and EVAL.
2828
// By default, SHA-1 is calculated client-side. Use WithLoadSHA1(true) option to load SHA-1 from Redis instead.
2929
func NewLuaScript(script string, opts ...LuaOption) *Lua {
30-
return newLuaScript(script, false, false, opts...)
30+
return newLuaScript(script, false, false, false, opts...)
3131
}
3232

3333
// NewLuaScriptReadOnly creates a Lua instance whose Lua.Exec uses EVALSHA_RO and EVAL_RO.
3434
// By default, SHA-1 is calculated client-side. Use WithLoadSHA1(true) option to load SHA-1 from Redis instead.
3535
func NewLuaScriptReadOnly(script string, opts ...LuaOption) *Lua {
36-
return newLuaScript(script, true, false, opts...)
36+
return newLuaScript(script, true, false, false, opts...)
3737
}
3838

3939
// NewLuaScriptNoSha creates a Lua instance whose Lua.Exec uses EVAL only (never EVALSHA).
4040
// No SHA-1 is calculated or loaded. The script is sent to the server every time. Use this when you want
4141
// to avoid SHA-1 entirely (e.g., to fully avoid hash collision concerns).
4242
func NewLuaScriptNoSha(script string) *Lua {
43-
return newLuaScript(script, false, true)
43+
return newLuaScript(script, false, true, false)
4444
}
4545

4646
// NewLuaScriptReadOnlyNoSha creates a Lua instance whose Lua.Exec uses EVAL_RO only (never EVALSHA_RO).
4747
// No SHA-1 is calculated or loaded. The script is sent to the server every time. Use this when you want
4848
// to avoid SHA-1 entirely (e.g., to fully avoid hash collision concerns).
4949
func NewLuaScriptReadOnlyNoSha(script string) *Lua {
50-
return newLuaScript(script, true, true)
50+
return newLuaScript(script, true, true, false)
5151
}
5252

53-
func newLuaScript(script string, readonly bool, noSha1 bool, opts ...LuaOption) *Lua {
53+
// NewLuaScriptRetryable creates a retryable Lua instance whose Lua.Exec uses EVALSHA and EVAL.
54+
// By default, SHA-1 is calculated client-side. Use WithLoadSHA1(true) option to load SHA-1 from Redis instead.
55+
func NewLuaScriptRetryable(script string, opts ...LuaOption) *Lua {
56+
return newLuaScript(script, false, false, true, opts...)
57+
}
58+
59+
// NewLuaScriptNoShaRetryable creates a retryable Lua instance whose Lua.Exec uses EVAL only (never EVALSHA).
60+
// No SHA-1 is calculated or loaded. The script is sent to the server every time. Use this when you want
61+
// to avoid SHA-1 entirely (e.g., to fully avoid hash collision concerns).
62+
func NewLuaScriptNoShaRetryable(script string) *Lua {
63+
return newLuaScript(script, false, true, true)
64+
}
65+
66+
func newLuaScript(script string, readonly bool, noSha1, retryable bool, opts ...LuaOption) *Lua {
5467
l := &Lua{
55-
script: script,
56-
maxp: runtime.GOMAXPROCS(0),
57-
readonly: readonly,
58-
noSha1: noSha1,
68+
script: script,
69+
maxp: runtime.GOMAXPROCS(0),
70+
readonly: readonly,
71+
noSha1: noSha1,
72+
retryable: retryable,
5973
}
6074
for _, opt := range opts {
6175
opt(l)
@@ -70,14 +84,15 @@ func newLuaScript(script string, readonly bool, noSha1 bool, opts ...LuaOption)
7084

7185
// Lua represents a redis lua script. It should be created from the NewLuaScript() or NewLuaScriptReadOnly().
7286
type Lua struct {
73-
script string
74-
sha1 string
75-
sha1Call call
76-
maxp int
77-
sha1Mu sync.RWMutex
78-
readonly bool
79-
noSha1 bool
80-
loadSha1 bool
87+
script string
88+
sha1 string
89+
sha1Call call
90+
maxp int
91+
sha1Mu sync.RWMutex
92+
readonly bool
93+
noSha1 bool
94+
loadSha1 bool
95+
retryable bool
8196
}
8297

8398
// Exec the script to the given Client.
@@ -99,7 +114,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
99114
// If not loaded yet, use singleflight to load it.
100115
if scriptSha1 == "" {
101116
err := s.sha1Call.Do(ctx, func() error {
102-
result := c.Do(ctx, c.B().ScriptLoad().Script(s.script).Build())
117+
result := c.Do(ctx, c.B().ScriptLoad().Script(s.script).Build().ToRetryable())
103118
if shaStr, err := result.ToString(); err == nil {
104119
s.sha1Mu.Lock()
105120
s.sha1 = shaStr
@@ -126,7 +141,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
126141
if s.readonly {
127142
resp = c.Do(ctx, c.B().EvalshaRo().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
128143
} else {
129-
resp = c.Do(ctx, c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
144+
resp = c.Do(ctx, s.mayRetryable(c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build()))
130145
}
131146
err, isErr := IsRedisErr(resp.Error())
132147
isNoScript = isErr && err.IsNoScript()
@@ -135,7 +150,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
135150
if s.readonly {
136151
resp = c.Do(ctx, c.B().EvalRo().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
137152
} else {
138-
resp = c.Do(ctx, c.B().Eval().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
153+
resp = c.Do(ctx, s.mayRetryable(c.B().Eval().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build()))
139154
}
140155
}
141156
return resp
@@ -159,7 +174,7 @@ func (s *Lua) ExecMulti(ctx context.Context, c Client, multi ...LuaExec) (resp [
159174
var e atomic.Value
160175
var sha1Result atomic.Value
161176
util.ParallelVals(s.maxp, c.Nodes(), func(n Client) {
162-
result := n.Do(ctx, n.B().ScriptLoad().Script(s.script).Build())
177+
result := n.Do(ctx, n.B().ScriptLoad().Script(s.script).Build().ToRetryable())
163178
if err := result.Error(); err != nil {
164179
e.CompareAndSwap(nil, &errs{error: err})
165180
} else if s.loadSha1 {
@@ -200,15 +215,22 @@ func (s *Lua) ExecMulti(ctx context.Context, c Client, multi ...LuaExec) (resp [
200215
if s.readonly {
201216
cmds = append(cmds, c.B().EvalshaRo().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
202217
} else {
203-
cmds = append(cmds, c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
218+
cmds = append(cmds, s.mayRetryable(c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build()))
204219
}
205220
} else {
206221
if s.readonly {
207222
cmds = append(cmds, c.B().EvalRo().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
208223
} else {
209-
cmds = append(cmds, c.B().Eval().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
224+
cmds = append(cmds, s.mayRetryable(c.B().Eval().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build()))
210225
}
211226
}
212227
}
213228
return c.DoMulti(ctx, cmds...)
214229
}
230+
231+
func (s *Lua) mayRetryable(cmd Completed) Completed {
232+
if s.retryable {
233+
cmd = cmd.ToRetryable()
234+
}
235+
return cmd
236+
}

0 commit comments

Comments
 (0)