Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 45 additions & 23 deletions lua.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,35 +27,49 @@ func WithLoadSHA1(enabled bool) LuaOption {
// NewLuaScript creates a Lua instance whose Lua.Exec uses EVALSHA and EVAL.
// By default, SHA-1 is calculated client-side. Use WithLoadSHA1(true) option to load SHA-1 from Redis instead.
func NewLuaScript(script string, opts ...LuaOption) *Lua {
return newLuaScript(script, false, false, opts...)
return newLuaScript(script, false, false, false, opts...)
}

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

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

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

func newLuaScript(script string, readonly bool, noSha1 bool, opts ...LuaOption) *Lua {
// NewLuaScriptRetryable creates a retryable Lua instance whose Lua.Exec uses EVALSHA and EVAL.
// By default, SHA-1 is calculated client-side. Use WithLoadSHA1(true) option to load SHA-1 from Redis instead.
func NewLuaScriptRetryable(script string, opts ...LuaOption) *Lua {
return newLuaScript(script, false, false, true, opts...)
}

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

func newLuaScript(script string, readonly bool, noSha1, retryable bool, opts ...LuaOption) *Lua {
l := &Lua{
script: script,
maxp: runtime.GOMAXPROCS(0),
readonly: readonly,
noSha1: noSha1,
script: script,
maxp: runtime.GOMAXPROCS(0),
readonly: readonly,
noSha1: noSha1,
retryable: retryable,
}
for _, opt := range opts {
opt(l)
Expand All @@ -70,14 +84,15 @@ func newLuaScript(script string, readonly bool, noSha1 bool, opts ...LuaOption)

// Lua represents a redis lua script. It should be created from the NewLuaScript() or NewLuaScriptReadOnly().
type Lua struct {
script string
sha1 string
sha1Call call
maxp int
sha1Mu sync.RWMutex
readonly bool
noSha1 bool
loadSha1 bool
script string
sha1 string
sha1Call call
maxp int
sha1Mu sync.RWMutex
readonly bool
noSha1 bool
loadSha1 bool
retryable bool
}

// Exec the script to the given Client.
Expand All @@ -99,7 +114,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
// If not loaded yet, use singleflight to load it.
if scriptSha1 == "" {
err := s.sha1Call.Do(ctx, func() error {
result := c.Do(ctx, c.B().ScriptLoad().Script(s.script).Build())
result := c.Do(ctx, c.B().ScriptLoad().Script(s.script).Build().ToRetryable())
if shaStr, err := result.ToString(); err == nil {
s.sha1Mu.Lock()
s.sha1 = shaStr
Expand All @@ -126,7 +141,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
if s.readonly {
resp = c.Do(ctx, c.B().EvalshaRo().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
} else {
resp = c.Do(ctx, c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
resp = c.Do(ctx, s.mayRetryable(c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build()))
}
err, isErr := IsRedisErr(resp.Error())
isNoScript = isErr && err.IsNoScript()
Expand All @@ -135,7 +150,7 @@ func (s *Lua) Exec(ctx context.Context, c Client, keys, args []string) (resp Red
if s.readonly {
resp = c.Do(ctx, c.B().EvalRo().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
} else {
resp = c.Do(ctx, c.B().Eval().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build())
resp = c.Do(ctx, s.mayRetryable(c.B().Eval().Script(s.script).Numkeys(int64(len(keys))).Key(keys...).Arg(args...).Build()))
}
}
return resp
Expand All @@ -159,7 +174,7 @@ func (s *Lua) ExecMulti(ctx context.Context, c Client, multi ...LuaExec) (resp [
var e atomic.Value
var sha1Result atomic.Value
util.ParallelVals(s.maxp, c.Nodes(), func(n Client) {
result := n.Do(ctx, n.B().ScriptLoad().Script(s.script).Build())
result := n.Do(ctx, n.B().ScriptLoad().Script(s.script).Build().ToRetryable())
if err := result.Error(); err != nil {
e.CompareAndSwap(nil, &errs{error: err})
} else if s.loadSha1 {
Expand Down Expand Up @@ -200,15 +215,22 @@ func (s *Lua) ExecMulti(ctx context.Context, c Client, multi ...LuaExec) (resp [
if s.readonly {
cmds = append(cmds, c.B().EvalshaRo().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
} else {
cmds = append(cmds, c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
cmds = append(cmds, s.mayRetryable(c.B().Evalsha().Sha1(scriptSha1).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build()))
}
} else {
if s.readonly {
cmds = append(cmds, c.B().EvalRo().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
} else {
cmds = append(cmds, c.B().Eval().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build())
cmds = append(cmds, s.mayRetryable(c.B().Eval().Script(s.script).Numkeys(int64(len(m.Keys))).Key(m.Keys...).Arg(m.Args...).Build()))
}
}
}
return c.DoMulti(ctx, cmds...)
}

func (s *Lua) mayRetryable(cmd Completed) Completed {
if s.retryable {
cmd = cmd.ToRetryable()
}
return cmd
}
Loading
Loading