mirror of https://github.com/go-redis/redis.git
Add withConn and set cmd errors more consistently
This commit is contained in:
parent
178deea321
commit
2a46cb006d
248
cluster.go
248
cluster.go
|
@ -745,14 +745,22 @@ func (c *ClusterClient) ProcessContext(ctx context.Context, cmd Cmder) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
err := c._process(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClusterClient) _process(ctx context.Context, cmd Cmder) error {
|
||||||
cmdInfo := c.cmdInfo(cmd.Name())
|
cmdInfo := c.cmdInfo(cmd.Name())
|
||||||
slot := c.cmdSlot(cmd)
|
slot := c.cmdSlot(cmd)
|
||||||
|
|
||||||
var node *clusterNode
|
var node *clusterNode
|
||||||
var ask bool
|
var ask bool
|
||||||
|
var lastErr error
|
||||||
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
||||||
var err error
|
|
||||||
|
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -760,10 +768,10 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if node == nil {
|
if node == nil {
|
||||||
|
var err error
|
||||||
node, err = c.cmdNode(cmdInfo, slot)
|
node, err = c.cmdNode(cmdInfo, slot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.setErr(err)
|
return err
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -771,23 +779,27 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
pipe := node.Client.Pipeline()
|
pipe := node.Client.Pipeline()
|
||||||
_ = pipe.Process(NewCmd("ASKING"))
|
_ = pipe.Process(NewCmd("ASKING"))
|
||||||
_ = pipe.Process(cmd)
|
_ = pipe.Process(cmd)
|
||||||
_, err = pipe.ExecContext(ctx)
|
_, lastErr = pipe.ExecContext(ctx)
|
||||||
_ = pipe.Close()
|
_ = pipe.Close()
|
||||||
ask = false
|
ask = false
|
||||||
} else {
|
} else {
|
||||||
err = node.Client.ProcessContext(ctx, cmd)
|
lastErr = node.Client._process(ctx, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there is no error - we are done.
|
// If there is no error - we are done.
|
||||||
if err == nil {
|
if lastErr == nil {
|
||||||
break
|
return nil
|
||||||
}
|
}
|
||||||
if err != Nil {
|
if lastErr != Nil {
|
||||||
c.state.LazyReload()
|
c.state.LazyReload()
|
||||||
}
|
}
|
||||||
|
if lastErr == pool.ErrClosed || isReadOnlyError(lastErr) {
|
||||||
|
node = nil
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// If slave is loading - pick another node.
|
// If slave is loading - pick another node.
|
||||||
if c.opt.ReadOnly && isLoadingError(err) {
|
if c.opt.ReadOnly && isLoadingError(lastErr) {
|
||||||
node.MarkAsFailing()
|
node.MarkAsFailing()
|
||||||
node = nil
|
node = nil
|
||||||
continue
|
continue
|
||||||
|
@ -795,21 +807,17 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
|
||||||
var moved bool
|
var moved bool
|
||||||
var addr string
|
var addr string
|
||||||
moved, ask, addr = isMovedError(err)
|
moved, ask, addr = isMovedError(lastErr)
|
||||||
if moved || ask {
|
if moved || ask {
|
||||||
|
var err error
|
||||||
node, err = c.nodes.Get(addr)
|
node, err = c.nodes.Get(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
break
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == pool.ErrClosed || isReadOnlyError(err) {
|
if isRetryableError(lastErr, cmd.readTimeout() == nil) {
|
||||||
node = nil
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if isRetryableError(err, cmd.readTimeout() == nil) {
|
|
||||||
// First retry the same node.
|
// First retry the same node.
|
||||||
if attempt == 0 {
|
if attempt == 0 {
|
||||||
continue
|
continue
|
||||||
|
@ -821,10 +829,9 @@ func (c *ClusterClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
return lastErr
|
||||||
}
|
}
|
||||||
|
return lastErr
|
||||||
return cmd.Err()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForEachMaster concurrently calls the fn on each master node in the cluster.
|
// ForEachMaster concurrently calls the fn on each master node in the cluster.
|
||||||
|
@ -1052,6 +1059,7 @@ func (c *ClusterClient) _processPipeline(ctx context.Context, cmds []Cmder) erro
|
||||||
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1064,18 +1072,24 @@ func (c *ClusterClient) _processPipeline(ctx context.Context, cmds []Cmder) erro
|
||||||
go func(node *clusterNode, cmds []Cmder) {
|
go func(node *clusterNode, cmds []Cmder) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
cn, err := node.Client.getConn(ctx)
|
err := node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return writeCmd(wr, cmds...)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == pool.ErrClosed {
|
return err
|
||||||
_ = c.mapCmdsByNode(cmds, failedCmds)
|
|
||||||
} else {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.pipelineProcessCmds(ctx, node, cn, cmds, failedCmds)
|
return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||||
node.Client.releaseConn(cn, err)
|
return c.pipelineReadCmds(node, rd, cmds, failedCmds)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = c.mapCmdsByNode(cmds, failedCmds)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}(node, cmds)
|
}(node, cmds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1100,10 +1114,15 @@ func newCmdsMap() *cmdsMap {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *cmdsMap) Add(node *clusterNode, cmds ...Cmder) {
|
||||||
|
m.mu.Lock()
|
||||||
|
m.m[node] = append(m.m[node], cmds...)
|
||||||
|
m.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error {
|
func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error {
|
||||||
state, err := c.state.Get()
|
state, err := c.state.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1122,10 +1141,7 @@ func (c *ClusterClient) mapCmdsByNode(cmds []Cmder, cmdsMap *cmdsMap) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
cmdsMap.Add(node, cmd)
|
||||||
cmdsMap.mu.Lock()
|
|
||||||
cmdsMap.m[node] = append(cmdsMap.m[node], cmd)
|
|
||||||
cmdsMap.mu.Unlock()
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1140,87 +1156,55 @@ func (c *ClusterClient) cmdsAreReadOnly(cmds []Cmder) bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterClient) pipelineProcessCmds(
|
|
||||||
ctx context.Context, node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap,
|
|
||||||
) error {
|
|
||||||
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return writeCmd(wr, cmds...)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
failedCmds.mu.Lock()
|
|
||||||
failedCmds.m[node] = cmds
|
|
||||||
failedCmds.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
|
||||||
return c.pipelineReadCmds(node, rd, cmds, failedCmds)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClusterClient) pipelineReadCmds(
|
func (c *ClusterClient) pipelineReadCmds(
|
||||||
node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap,
|
node *clusterNode, rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap,
|
||||||
) error {
|
) error {
|
||||||
var firstErr error
|
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
err := cmd.readReply(rd)
|
err := cmd.readReply(rd)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.checkMovedErr(cmd, err, failedCmds) {
|
if c.checkMovedErr(cmd, err, failedCmds) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.opt.ReadOnly && isLoadingError(err) {
|
if c.opt.ReadOnly && isLoadingError(err) {
|
||||||
node.MarkAsFailing()
|
node.MarkAsFailing()
|
||||||
} else if isRedisError(err) {
|
return err
|
||||||
|
}
|
||||||
|
if isRedisError(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
return err
|
||||||
failedCmds.mu.Lock()
|
|
||||||
failedCmds.m[node] = append(failedCmds.m[node], cmd)
|
|
||||||
failedCmds.mu.Unlock()
|
|
||||||
if firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
}
|
||||||
}
|
return nil
|
||||||
return firstErr
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterClient) checkMovedErr(
|
func (c *ClusterClient) checkMovedErr(
|
||||||
cmd Cmder, err error, failedCmds *cmdsMap,
|
cmd Cmder, err error, failedCmds *cmdsMap,
|
||||||
) bool {
|
) bool {
|
||||||
moved, ask, addr := isMovedError(err)
|
moved, ask, addr := isMovedError(err)
|
||||||
|
if !moved && !ask {
|
||||||
if moved {
|
return false
|
||||||
c.state.LazyReload()
|
}
|
||||||
|
|
||||||
node, err := c.nodes.Get(addr)
|
node, err := c.nodes.Get(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
failedCmds.mu.Lock()
|
if moved {
|
||||||
failedCmds.m[node] = append(failedCmds.m[node], cmd)
|
c.state.LazyReload()
|
||||||
failedCmds.mu.Unlock()
|
failedCmds.Add(node, cmd)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if ask {
|
if ask {
|
||||||
node, err := c.nodes.Get(addr)
|
failedCmds.Add(node, NewCmd("ASKING"), cmd)
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
failedCmds.mu.Lock()
|
|
||||||
failedCmds.m[node] = append(failedCmds.m[node], NewCmd("ASKING"), cmd)
|
|
||||||
failedCmds.mu.Unlock()
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
panic("not reached")
|
||||||
}
|
}
|
||||||
|
|
||||||
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
||||||
|
@ -1244,6 +1228,7 @@ func (c *ClusterClient) processTxPipeline(ctx context.Context, cmds []Cmder) err
|
||||||
func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) error {
|
||||||
state, err := c.state.Get()
|
state, err := c.state.Get()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1254,11 +1239,12 @@ func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) er
|
||||||
setCmdsErr(cmds, err)
|
setCmdsErr(cmds, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
cmdsMap := map[*clusterNode][]Cmder{node: cmds}
|
|
||||||
|
|
||||||
|
cmdsMap := map[*clusterNode][]Cmder{node: cmds}
|
||||||
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRedirects; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1271,18 +1257,33 @@ func (c *ClusterClient) _processTxPipeline(ctx context.Context, cmds []Cmder) er
|
||||||
go func(node *clusterNode, cmds []Cmder) {
|
go func(node *clusterNode, cmds []Cmder) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
cn, err := node.Client.getConn(ctx)
|
err := node.Client.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
|
return txPipelineWriteMulti(wr, cmds)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == pool.ErrClosed {
|
return err
|
||||||
_ = c.mapCmdsByNode(cmds, failedCmds)
|
|
||||||
} else {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.txPipelineProcessCmds(ctx, node, cn, cmds, failedCmds)
|
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||||
node.Client.releaseConn(cn, err)
|
err := c.txPipelineReadQueued(rd, cmds, failedCmds)
|
||||||
|
if err != nil {
|
||||||
|
moved, ask, addr := isMovedError(err)
|
||||||
|
if moved || ask {
|
||||||
|
return c.cmdsMoved(cmds, moved, ask, addr, failedCmds)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return pipelineReadCmds(rd, cmds)
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
err = c.mapCmdsByNode(cmds, failedCmds)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
}(node, cmds)
|
}(node, cmds)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1306,31 +1307,6 @@ func (c *ClusterClient) mapCmdsBySlot(cmds []Cmder) map[int][]Cmder {
|
||||||
return cmdsMap
|
return cmdsMap
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ClusterClient) txPipelineProcessCmds(
|
|
||||||
ctx context.Context, node *clusterNode, cn *pool.Conn, cmds []Cmder, failedCmds *cmdsMap,
|
|
||||||
) error {
|
|
||||||
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return txPipelineWriteMulti(wr, cmds)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
failedCmds.mu.Lock()
|
|
||||||
failedCmds.m[node] = cmds
|
|
||||||
failedCmds.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
|
||||||
err := c.txPipelineReadQueued(rd, cmds, failedCmds)
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pipelineReadCmds(rd, cmds)
|
|
||||||
})
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClusterClient) txPipelineReadQueued(
|
func (c *ClusterClient) txPipelineReadQueued(
|
||||||
rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap,
|
rd *proto.Reader, cmds []Cmder, failedCmds *cmdsMap,
|
||||||
) error {
|
) error {
|
||||||
|
@ -1342,14 +1318,9 @@ func (c *ClusterClient) txPipelineReadQueued(
|
||||||
|
|
||||||
for _, cmd := range cmds {
|
for _, cmd := range cmds {
|
||||||
err := statusCmd.readReply(rd)
|
err := statusCmd.readReply(rd)
|
||||||
if err == nil {
|
if err == nil || c.checkMovedErr(cmd, err, failedCmds) || isRedisError(err) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.checkMovedErr(cmd, err, failedCmds) || isRedisError(err) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1364,20 +1335,39 @@ func (c *ClusterClient) txPipelineReadQueued(
|
||||||
|
|
||||||
switch line[0] {
|
switch line[0] {
|
||||||
case proto.ErrorReply:
|
case proto.ErrorReply:
|
||||||
err := proto.ParseErrorReply(line)
|
return proto.ParseErrorReply(line)
|
||||||
for _, cmd := range cmds {
|
|
||||||
if !c.checkMovedErr(cmd, err, failedCmds) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
case proto.ArrayReply:
|
case proto.ArrayReply:
|
||||||
// ok
|
// ok
|
||||||
default:
|
default:
|
||||||
err := fmt.Errorf("redis: expected '*', but got line %q", line)
|
return fmt.Errorf("redis: expected '*', but got line %q", line)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClusterClient) cmdsMoved(
|
||||||
|
cmds []Cmder, moved, ask bool, addr string, failedCmds *cmdsMap,
|
||||||
|
) error {
|
||||||
|
node, err := c.nodes.Get(addr)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if moved {
|
||||||
|
c.state.LazyReload()
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
failedCmds.Add(node, cmd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if ask {
|
||||||
|
for _, cmd := range cmds {
|
||||||
|
failedCmds.Add(node, NewCmd("ASKING"), cmd)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,10 @@ func (cmd *baseCmd) stringArg(pos int) string {
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cmd *baseCmd) setErr(e error) {
|
||||||
|
cmd.err = e
|
||||||
|
}
|
||||||
|
|
||||||
func (cmd *baseCmd) Err() error {
|
func (cmd *baseCmd) Err() error {
|
||||||
return cmd.err
|
return cmd.err
|
||||||
}
|
}
|
||||||
|
@ -135,10 +139,6 @@ func (cmd *baseCmd) setReadTimeout(d time.Duration) {
|
||||||
cmd._readTimeout = &d
|
cmd._readTimeout = &d
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cmd *baseCmd) setErr(e error) {
|
|
||||||
cmd.err = e
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
type Cmd struct {
|
type Cmd struct {
|
||||||
|
|
93
redis.go
93
redis.go
|
@ -235,7 +235,32 @@ func (c *baseClient) releaseConn(cn *pool.Conn, err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) withConn(
|
||||||
|
ctx context.Context, fn func(context.Context, *pool.Conn) error,
|
||||||
|
) error {
|
||||||
|
cn, err := c.getConn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
c.releaseConn(cn, err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
err = fn(ctx, cn)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
err := c._process(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) _process(ctx context.Context, cmd Cmder) error {
|
||||||
|
var lastErr error
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
@ -243,37 +268,29 @@ func (c *baseClient) process(ctx context.Context, cmd Cmder) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, err := c.getConn(ctx)
|
var retryTimeout bool
|
||||||
if err != nil {
|
lastErr = c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
cmd.setErr(err)
|
err := cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
||||||
if isRetryableError(err, true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithWriter(ctx, c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return writeCmd(wr, cmd)
|
return writeCmd(wr, cmd)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.releaseConn(cn, err)
|
retryTimeout = true
|
||||||
cmd.setErr(err)
|
|
||||||
if isRetryableError(err, true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cn.WithReader(ctx, c.cmdTimeout(cmd), cmd.readReply)
|
err = cn.WithReader(ctx, c.cmdTimeout(cmd), cmd.readReply)
|
||||||
c.releaseConn(cn, err)
|
if err != nil {
|
||||||
if err != nil && isRetryableError(err, cmd.readTimeout() == nil) {
|
retryTimeout = cmd.readTimeout() == nil
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmd.Err()
|
return nil
|
||||||
|
})
|
||||||
|
if lastErr == nil || !isRetryableError(lastErr, retryTimeout) {
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
||||||
|
@ -325,6 +342,18 @@ type pipelineProcessor func(context.Context, *pool.Conn, []Cmder) (bool, error)
|
||||||
func (c *baseClient) generalProcessPipeline(
|
func (c *baseClient) generalProcessPipeline(
|
||||||
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
||||||
) error {
|
) error {
|
||||||
|
err := c._generalProcessPipeline(ctx, cmds, p)
|
||||||
|
if err != nil {
|
||||||
|
setCmdsErr(cmds, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return cmdsFirstErr(cmds)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *baseClient) _generalProcessPipeline(
|
||||||
|
ctx context.Context, cmds []Cmder, p pipelineProcessor,
|
||||||
|
) error {
|
||||||
|
var lastErr error
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
@ -332,20 +361,17 @@ func (c *baseClient) generalProcessPipeline(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cn, err := c.getConn(ctx)
|
var canRetry bool
|
||||||
if err != nil {
|
lastErr = c.withConn(ctx, func(ctx context.Context, cn *pool.Conn) error {
|
||||||
setCmdsErr(cmds, err)
|
var err error
|
||||||
|
canRetry, err = p(ctx, cn, cmds)
|
||||||
return err
|
return err
|
||||||
}
|
})
|
||||||
|
if lastErr == nil || !canRetry || !isRetryableError(lastErr, true) {
|
||||||
canRetry, err := p(ctx, cn, cmds)
|
return lastErr
|
||||||
c.releaseConn(cn, err)
|
|
||||||
|
|
||||||
if !canRetry || !isRetryableError(err, true) {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cmdsFirstErr(cmds)
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *baseClient) pipelineProcessCmds(
|
func (c *baseClient) pipelineProcessCmds(
|
||||||
|
@ -355,7 +381,6 @@ func (c *baseClient) pipelineProcessCmds(
|
||||||
return writeCmd(wr, cmds...)
|
return writeCmd(wr, cmds...)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,14 +407,12 @@ func (c *baseClient) txPipelineProcessCmds(
|
||||||
return txPipelineWriteMulti(wr, cmds)
|
return txPipelineWriteMulti(wr, cmds)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return true, err
|
return true, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
err = cn.WithReader(ctx, c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
||||||
err := txPipelineReadQueued(rd, cmds)
|
err := txPipelineReadQueued(rd, cmds)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return pipelineReadCmds(rd, cmds)
|
return pipelineReadCmds(rd, cmds)
|
||||||
|
|
81
ring.go
81
ring.go
|
@ -551,6 +551,16 @@ func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) process(ctx context.Context, cmd Cmder) error {
|
func (c *Ring) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
err := c._process(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
cmd.setErr(err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Ring) _process(ctx context.Context, cmd Cmder) error {
|
||||||
|
var lastErr error
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
||||||
if attempt > 0 {
|
if attempt > 0 {
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
||||||
|
@ -560,19 +570,15 @@ func (c *Ring) process(ctx context.Context, cmd Cmder) error {
|
||||||
|
|
||||||
shard, err := c.cmdShard(cmd)
|
shard, err := c.cmdShard(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd.setErr(err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = shard.Client.ProcessContext(ctx, cmd)
|
lastErr = shard.Client._process(ctx, cmd)
|
||||||
if err == nil {
|
if lastErr == nil || !isRetryableError(lastErr, cmd.readTimeout() == nil) {
|
||||||
return nil
|
return lastErr
|
||||||
}
|
|
||||||
if !isRetryableError(err, cmd.readTimeout() == nil) {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cmd.Err()
|
return lastErr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
||||||
|
@ -626,61 +632,40 @@ func (c *Ring) generalProcessPipeline(
|
||||||
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
if err := internal.Sleep(ctx, c.retryBackoff(attempt)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
|
||||||
var failedCmdsMap map[string][]Cmder
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for hash, cmds := range cmdsMap {
|
for hash, cmds := range cmdsMap {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(hash string, cmds []Cmder) {
|
go func(hash string, cmds []Cmder) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
shard, err := c.shards.GetByHash(hash)
|
err := c.processShardPipeline(ctx, hash, cmds, tx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
setCmdsErr(cmds, err)
|
setCmdsErr(cmds, err)
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := shard.Client.getConn(ctx)
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var canRetry bool
|
|
||||||
if tx {
|
|
||||||
canRetry, err = shard.Client.txPipelineProcessCmds(ctx, cn, cmds)
|
|
||||||
} else {
|
|
||||||
canRetry, err = shard.Client.pipelineProcessCmds(ctx, cn, cmds)
|
|
||||||
}
|
|
||||||
shard.Client.releaseConn(cn, err)
|
|
||||||
|
|
||||||
if canRetry && isRetryableError(err, true) {
|
|
||||||
mu.Lock()
|
|
||||||
if failedCmdsMap == nil {
|
|
||||||
failedCmdsMap = make(map[string][]Cmder)
|
|
||||||
}
|
|
||||||
failedCmdsMap[hash] = cmds
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
}
|
||||||
}(hash, cmds)
|
}(hash, cmds)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
if len(failedCmdsMap) == 0 {
|
return cmdsFirstErr(cmds)
|
||||||
break
|
}
|
||||||
}
|
|
||||||
cmdsMap = failedCmdsMap
|
func (c *Ring) processShardPipeline(
|
||||||
|
ctx context.Context, hash string, cmds []Cmder, tx bool,
|
||||||
|
) error {
|
||||||
|
//TODO: retry?
|
||||||
|
shard, err := c.shards.GetByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmdsFirstErr(cmds)
|
if tx {
|
||||||
|
err = shard.Client._generalProcessPipeline(
|
||||||
|
ctx, cmds, shard.Client.txPipelineProcessCmds)
|
||||||
|
} else {
|
||||||
|
err = shard.Client._generalProcessPipeline(
|
||||||
|
ctx, cmds, shard.Client.pipelineProcessCmds)
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Close closes the ring client, releasing any open resources.
|
// Close closes the ring client, releasing any open resources.
|
||||||
|
|
Loading…
Reference in New Issue