mirror of
https://github.com/redis/go-redis.git
synced 2025-07-28 06:42:00 +03:00
Add TxPipeline.
This commit is contained in:
194
redis.go
194
redis.go
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"gopkg.in/redis.v5/internal"
|
||||
"gopkg.in/redis.v5/internal/pool"
|
||||
"gopkg.in/redis.v5/internal/proto"
|
||||
)
|
||||
|
||||
// Redis nil reply, .e.g. when key does not exist.
|
||||
@ -96,10 +97,6 @@ func (c *baseClient) WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(
|
||||
|
||||
func (c *baseClient) defaultProcess(cmd Cmder) error {
|
||||
for i := 0; i <= c.opt.MaxRetries; i++ {
|
||||
if i > 0 {
|
||||
cmd.reset()
|
||||
}
|
||||
|
||||
cn, _, err := c.conn()
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
@ -162,6 +159,129 @@ func (c *baseClient) getAddr() string {
|
||||
return c.opt.Addr
|
||||
}
|
||||
|
||||
type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error)
|
||||
|
||||
func (c *baseClient) pipelineExecer(p pipelineProcessor) pipelineExecer {
|
||||
return func(cmds []Cmder) error {
|
||||
var firstErr error
|
||||
for i := 0; i <= c.opt.MaxRetries; i++ {
|
||||
cn, _, err := c.conn()
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return err
|
||||
}
|
||||
|
||||
canRetry, err := p(cn, cmds)
|
||||
c.putConn(cn, err, false)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
if !canRetry || !internal.IsRetryableError(err) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
}
|
||||
|
||||
func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (retry bool, firstErr error) {
|
||||
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||
if err := writeCmd(cn, cmds...); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Set read timeout for all commands.
|
||||
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||
return pipelineReadCmds(cn, cmds)
|
||||
}
|
||||
|
||||
func pipelineReadCmds(cn *pool.Conn, cmds []Cmder) (retry bool, firstErr error) {
|
||||
for i, cmd := range cmds {
|
||||
err := cmd.readReply(cn)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if i == 0 {
|
||||
retry = true
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return false, firstErr
|
||||
}
|
||||
|
||||
func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) {
|
||||
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||
if err := txPipelineWriteMulti(cn, cmds); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Set read timeout for all commands.
|
||||
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||
|
||||
if err := c.txPipelineReadQueued(cn, cmds); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
_, err := pipelineReadCmds(cn, cmds)
|
||||
return false, err
|
||||
}
|
||||
|
||||
func txPipelineWriteMulti(cn *pool.Conn, cmds []Cmder) error {
|
||||
multiExec := make([]Cmder, 0, len(cmds)+2)
|
||||
multiExec = append(multiExec, NewStatusCmd("MULTI"))
|
||||
multiExec = append(multiExec, cmds...)
|
||||
multiExec = append(multiExec, NewSliceCmd("EXEC"))
|
||||
return writeCmd(cn, multiExec...)
|
||||
}
|
||||
|
||||
func (c *baseClient) txPipelineReadQueued(cn *pool.Conn, cmds []Cmder) error {
|
||||
var firstErr error
|
||||
|
||||
// Parse queued replies.
|
||||
var statusCmd StatusCmd
|
||||
if err := statusCmd.readReply(cn); err != nil && firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
|
||||
for _, cmd := range cmds {
|
||||
err := statusCmd.readReply(cn)
|
||||
if err != nil {
|
||||
cmd.setErr(err)
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse number of replies.
|
||||
line, err := cn.Rd.ReadLine()
|
||||
if err != nil {
|
||||
if err == Nil {
|
||||
err = TxFailedErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
switch line[0] {
|
||||
case proto.ErrorReply:
|
||||
return proto.ParseErrorReply(line)
|
||||
case proto.ArrayReply:
|
||||
// ok
|
||||
default:
|
||||
err := fmt.Errorf("redis: expected '*', but got line %q", line)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Client is a Redis client representing a pool of zero or more
|
||||
@ -202,70 +322,30 @@ func (c *Client) PoolStats() *PoolStats {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) Pipelined(fn func(*Pipeline) error) ([]Cmder, error) {
|
||||
return c.Pipeline().pipelined(fn)
|
||||
}
|
||||
|
||||
func (c *Client) Pipeline() *Pipeline {
|
||||
pipe := Pipeline{
|
||||
exec: c.pipelineExec,
|
||||
exec: c.pipelineExecer(c.pipelineProcessCmds),
|
||||
}
|
||||
pipe.cmdable.process = pipe.Process
|
||||
pipe.statefulCmdable.process = pipe.Process
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Client) Pipelined(fn func(*Pipeline) error) ([]Cmder, error) {
|
||||
return c.Pipeline().pipelined(fn)
|
||||
func (c *Client) TxPipelined(fn func(*Pipeline) error) ([]Cmder, error) {
|
||||
return c.TxPipeline().pipelined(fn)
|
||||
}
|
||||
|
||||
func (c *Client) pipelineExec(cmds []Cmder) error {
|
||||
var firstErr error
|
||||
for i := 0; i <= c.opt.MaxRetries; i++ {
|
||||
if i > 0 {
|
||||
resetCmds(cmds)
|
||||
}
|
||||
|
||||
cn, _, err := c.conn()
|
||||
if err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return err
|
||||
}
|
||||
|
||||
retry, err := c.execCmds(cn, cmds)
|
||||
c.putConn(cn, err, false)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
if !retry {
|
||||
break
|
||||
}
|
||||
func (c *Client) TxPipeline() *Pipeline {
|
||||
pipe := Pipeline{
|
||||
exec: c.pipelineExecer(c.txPipelineProcessCmds),
|
||||
}
|
||||
return firstErr
|
||||
}
|
||||
|
||||
func (c *Client) execCmds(cn *pool.Conn, cmds []Cmder) (retry bool, firstErr error) {
|
||||
cn.SetWriteTimeout(c.opt.WriteTimeout)
|
||||
if err := writeCmd(cn, cmds...); err != nil {
|
||||
setCmdsErr(cmds, err)
|
||||
return true, err
|
||||
}
|
||||
|
||||
// Set read timeout for all commands.
|
||||
cn.SetReadTimeout(c.opt.ReadTimeout)
|
||||
|
||||
for i, cmd := range cmds {
|
||||
err := cmd.readReply(cn)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
if i == 0 && internal.IsNetworkError(err) {
|
||||
return true, err
|
||||
}
|
||||
if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
return false, firstErr
|
||||
pipe.cmdable.process = pipe.Process
|
||||
pipe.statefulCmdable.process = pipe.Process
|
||||
return &pipe
|
||||
}
|
||||
|
||||
func (c *Client) pubSub() *PubSub {
|
||||
|
Reference in New Issue
Block a user