Skip to content

Commit 383a2a1

Browse files
wyxloadingwuyuxiang
andauthored
fix: single slot DoMulti writes should be retried on MOVED / ASK (#697)
Co-authored-by: wuyuxiang <[email protected]>
1 parent 2246f91 commit 383a2a1

File tree

2 files changed

+98
-55
lines changed

2 files changed

+98
-55
lines changed

cluster.go

Lines changed: 29 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -555,35 +555,54 @@ func (c *clusterClient) _pickMulti(multi []Completed) (retries *connretry, last
555555
return retries, last, toReplica
556556
}
557557

558+
inits := 0
558559
for _, cmd := range multi {
559560
if cmd.Slot() == cmds.InitSlot {
561+
inits++
560562
continue
561563
}
564+
if last == cmds.InitSlot {
565+
last = cmd.Slot()
566+
} else if init && last != cmd.Slot() {
567+
panic(panicMixCxSlot)
568+
}
562569
p := c.pslots[cmd.Slot()]
563570
if p == nil {
564571
return nil, 0, false
565572
}
566573
count.m[p]++
567574
}
568575

576+
if last == cmds.InitSlot {
577+
// if all commands have no slots, such as INFO, we pick a non-nil slot.
578+
for i, p := range c.pslots {
579+
if p != nil {
580+
last = uint16(i)
581+
count.m[p] = inits
582+
break
583+
}
584+
}
585+
if last == cmds.InitSlot {
586+
return nil, 0, false
587+
}
588+
}
589+
569590
retries = connretryp.Get(len(count.m), len(count.m))
570591
for cc, n := range count.m {
571592
retries.m[cc] = retryp.Get(0, n)
572593
}
573594
conncountp.Put(count)
574595

575596
for i, cmd := range multi {
597+
var cc conn
576598
if cmd.Slot() != cmds.InitSlot {
577-
if last == cmds.InitSlot {
578-
last = cmd.Slot()
579-
} else if init && last != cmd.Slot() {
580-
panic(panicMixCxSlot)
581-
}
582-
cc := c.pslots[cmd.Slot()]
583-
re := retries.m[cc]
584-
re.commands = append(re.commands, cmd)
585-
re.cIndexes = append(re.cIndexes, i)
599+
cc = c.pslots[cmd.Slot()]
600+
} else {
601+
cc = c.pslots[last]
586602
}
603+
re := retries.m[cc]
604+
re.commands = append(re.commands, cmd)
605+
re.cIndexes = append(re.cIndexes, i)
587606
}
588607
return retries, last, false
589608
}
@@ -669,19 +688,12 @@ func (c *clusterClient) DoMulti(ctx context.Context, multi ...Completed) []Redis
669688
return nil
670689
}
671690

672-
retries, slot, toReplica, err := c.pickMulti(ctx, multi)
691+
retries, _, _, err := c.pickMulti(ctx, multi)
673692
if err != nil {
674693
return fillErrs(len(multi), err)
675694
}
676695
defer connretryp.Put(retries)
677696

678-
if len(retries.m) <= 1 {
679-
for _, re := range retries.m {
680-
retryp.Put(re)
681-
}
682-
return c.doMulti(ctx, slot, multi, toReplica)
683-
}
684-
685697
var wg sync.WaitGroup
686698
var mu sync.Mutex
687699

@@ -730,44 +742,6 @@ func fillErrs(n int, err error) (results []RedisResult) {
730742
return results
731743
}
732744

733-
func (c *clusterClient) doMulti(ctx context.Context, slot uint16, multi []Completed, toReplica bool) []RedisResult {
734-
attempts := 1
735-
736-
retry:
737-
cc, err := c.pick(ctx, slot, toReplica)
738-
if err != nil {
739-
return fillErrs(len(multi), err)
740-
}
741-
resps := cc.DoMulti(ctx, multi...)
742-
process:
743-
for i, resp := range resps.s {
744-
switch addr, mode := c.shouldRefreshRetry(resp.Error(), ctx); mode {
745-
case RedirectMove:
746-
if c.retry && allReadOnly(multi) {
747-
resultsp.Put(resps)
748-
resps = c.redirectOrNew(addr, cc, multi[i].Slot(), mode).DoMulti(ctx, multi...)
749-
goto process
750-
}
751-
case RedirectAsk:
752-
if c.retry && allReadOnly(multi) {
753-
resultsp.Put(resps)
754-
resps = askingMulti(c.redirectOrNew(addr, cc, multi[i].Slot(), mode), ctx, multi)
755-
goto process
756-
}
757-
case RedirectRetry:
758-
if c.retry && allReadOnly(multi) {
759-
shouldRetry := c.retryHandler.WaitOrSkipRetry(ctx, attempts, multi[i], resp.Error())
760-
if shouldRetry {
761-
resultsp.Put(resps)
762-
attempts++
763-
goto retry
764-
}
765-
}
766-
}
767-
}
768-
return resps.s
769-
}
770-
771745
func (c *clusterClient) doCache(ctx context.Context, cmd Cacheable, ttl time.Duration) (resp RedisResult) {
772746
attempts := 1
773747

cluster_test.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5239,3 +5239,72 @@ func TestClusterClientLoadingRetry(t *testing.T) {
52395239
}
52405240
})
52415241
}
5242+
5243+
func TestClusterClientMovedRetry(t *testing.T) {
5244+
defer ShouldNotLeaked(SetupLeakDetection())
5245+
5246+
setup := func() (*clusterClient, *mockConn) {
5247+
m := &mockConn{
5248+
DoFn: func(cmd Completed) RedisResult {
5249+
if strings.Join(cmd.Commands(), " ") == "CLUSTER SLOTS" {
5250+
return slotsMultiResp
5251+
}
5252+
return RedisResult{}
5253+
},
5254+
}
5255+
client, err := newClusterClient(
5256+
&ClientOption{InitAddress: []string{":0"}},
5257+
func(dst string, opt *ClientOption) conn { return m },
5258+
newRetryer(defaultRetryDelayFn),
5259+
)
5260+
if err != nil {
5261+
t.Fatalf("unexpected err %v", err)
5262+
}
5263+
return client, m
5264+
}
5265+
5266+
t.Run("DoMulti Retry on MOVED", func(t *testing.T) {
5267+
client, m := setup()
5268+
5269+
attempts := 0
5270+
m.DoMultiFn = func(multi ...Completed) *redisresults {
5271+
attempts++
5272+
if attempts == 1 {
5273+
return &redisresults{s: []RedisResult{newResult(RedisMessage{typ: '-', string: "MOVED 0 127.0.0.1"}, nil)}}
5274+
}
5275+
return &redisresults{s: []RedisResult{newResult(RedisMessage{typ: '+', string: "OK"}, nil)}}
5276+
}
5277+
5278+
cmd := client.B().Set().Key("test").Value(`test`).Build()
5279+
resps := client.DoMulti(context.Background(), cmd)
5280+
if len(resps) != 1 {
5281+
t.Fatalf("unexpected response length %v", len(resps))
5282+
}
5283+
if v, err := resps[0].ToString(); err != nil || v != "OK" {
5284+
t.Fatalf("unexpected response %v %v", v, err)
5285+
}
5286+
})
5287+
5288+
t.Run("DoMulti Retry on ASK", func(t *testing.T) {
5289+
client, m := setup()
5290+
5291+
attempts := 0
5292+
m.DoMultiFn = func(multi ...Completed) *redisresults {
5293+
attempts++
5294+
if attempts == 1 {
5295+
return &redisresults{s: []RedisResult{newResult(RedisMessage{typ: '-', string: "ASK 0 127.0.0.1"}, nil)}}
5296+
}
5297+
return &redisresults{s: []RedisResult{newResult(RedisMessage{typ: '+', string: "OK"}, nil), newResult(RedisMessage{typ: '+', string: "OK"}, nil)}}
5298+
}
5299+
5300+
cmd := client.B().Set().Key("test").Value(`test`).Build()
5301+
resps := client.DoMulti(context.Background(), cmd)
5302+
if len(resps) != 1 {
5303+
t.Fatalf("unexpected response length %v", len(resps))
5304+
}
5305+
if v, err := resps[0].ToString(); err != nil || v != "OK" {
5306+
t.Fatalf("unexpected response %v %v", v, err)
5307+
}
5308+
})
5309+
5310+
}

0 commit comments

Comments
 (0)