1
0
mirror of https://github.com/redis/go-redis.git synced 2026-10-22 19:49:00 +03:00

fix(command): read past error elements in slices (#4137)

Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com>
This commit is contained in:
Lazizbek Ergashev
2026-10-19 23:27:29 +05:00
committed by GitHub
parent 46da7e22c8
commit 5a9916dd5a
2 changed files with 96 additions and 8 deletions

View File

@@ -1673,17 +1673,23 @@ func (cmd *UintSliceCmd) readReply(rd *proto.Reader) error {
return err
}
cmd.val = make([]uint64, n)
var elemErr error
for i := range cmd.val {
switch num, err := rd.ReadUint(); {
case err == Nil:
cmd.val[i] = 0
case err != nil:
return err
if !isRedisError(err) {
return err
}
if elemErr == nil {
elemErr = err
}
default:
cmd.val[i] = num
}
}
return nil
return elemErr
}
func (cmd *UintSliceCmd) Clone() Cmder {
@@ -2104,17 +2110,23 @@ func (cmd *FloatSliceCmd) readReply(rd *proto.Reader) error {
}
cmd.val = make([]float64, n)
var elemErr error
for i := 0; i < len(cmd.val); i++ {
switch num, err := rd.ReadFloat(); {
case err == Nil:
cmd.val[i] = 0
case err != nil:
return err
if !isRedisError(err) {
return err
}
if elemErr == nil {
elemErr = err
}
default:
cmd.val[i] = num
}
}
return nil
return elemErr
}
func (cmd *FloatSliceCmd) Clone() Cmder {
@@ -2179,17 +2191,23 @@ func (cmd *StringSliceCmd) readReply(rd *proto.Reader) error {
return err
}
cmd.val = make([]string, n)
var elemErr error
for i := 0; i < len(cmd.val); i++ {
switch s, err := rd.ReadString(); {
case err == Nil:
cmd.val[i] = ""
case err != nil:
return err
if !isRedisError(err) {
return err
}
if elemErr == nil {
elemErr = err
}
default:
cmd.val[i] = s
}
}
return nil
return elemErr
}
func (cmd *StringSliceCmd) Clone() Cmder {
@@ -2446,17 +2464,23 @@ func (cmd *BoolSliceCmd) readReply(rd *proto.Reader) error {
return err
}
cmd.val = make([]bool, n)
var elemErr error
for i := 0; i < len(cmd.val); i++ {
switch b, err := rd.ReadBool(); {
case err == Nil:
cmd.val[i] = false
case err != nil:
return err
if !isRedisError(err) {
return err
}
if elemErr == nil {
elemErr = err
}
default:
cmd.val[i] = b
}
}
return nil
return elemErr
}
func (cmd *BoolSliceCmd) Clone() Cmder {

View File

@@ -56,6 +56,70 @@ func TestSliceCmdNilElementNoDesync(t *testing.T) {
})
}
// An error element in a non-final position is a complete frame, e.g. EVAL with
// `return {'a', redis.error_reply('boom'), 'c'}`. UintSliceCmd, FloatSliceCmd,
// StringSliceCmd and BoolSliceCmd used to return on it with the trailing
// elements still on the wire, so in a pipeline the next command read them as
// its reply. They must consume the whole array and return the first error.
func TestSliceCmdErrorElementNoDesync(t *testing.T) {
t.Run("UintSliceCmd", func(t *testing.T) {
rd := proto.NewReader(strings.NewReader("*3\r\n:255\r\n-ERR item failed\r\n:100\r\n:999\r\n"))
cmd := NewUintSliceCmd(context.Background())
if err := cmd.readReply(rd); err == nil || err.Error() != "ERR item failed" {
t.Fatalf("readReply err = %v, want ERR item failed", err)
}
if got := cmd.val; len(got) != 3 || got[0] != 255 || got[1] != 0 || got[2] != 100 {
t.Fatalf("unexpected val: %+v", got)
}
assertNextReplyInt(t, rd, 999)
})
t.Run("FloatSliceCmd", func(t *testing.T) {
rd := proto.NewReader(strings.NewReader("*3\r\n,1.5\r\n!15\r\nERR item failed\r\n,2.5\r\n:999\r\n"))
cmd := NewFloatSliceCmd(context.Background())
if err := cmd.readReply(rd); err == nil || err.Error() != "ERR item failed" {
t.Fatalf("readReply err = %v, want ERR item failed", err)
}
if got := cmd.val; len(got) != 3 || got[0] != 1.5 || got[1] != 0 || got[2] != 2.5 {
t.Fatalf("unexpected val: %+v", got)
}
assertNextReplyInt(t, rd, 999)
})
t.Run("StringSliceCmd", func(t *testing.T) {
rd := proto.NewReader(strings.NewReader("*4\r\n$1\r\na\r\n-ERR first\r\n-ERR second\r\n$1\r\nc\r\n:999\r\n"))
cmd := NewStringSliceCmd(context.Background())
if err := cmd.readReply(rd); err == nil || err.Error() != "ERR first" {
t.Fatalf("readReply err = %v, want ERR first", err)
}
if got := cmd.val; len(got) != 4 || got[0] != "a" || got[1] != "" || got[2] != "" || got[3] != "c" {
t.Fatalf("unexpected val: %+v", got)
}
assertNextReplyInt(t, rd, 999)
})
t.Run("BoolSliceCmd", func(t *testing.T) {
rd := proto.NewReader(strings.NewReader("*3\r\n:1\r\n-ERR item failed\r\n:1\r\n:999\r\n"))
cmd := NewBoolSliceCmd(context.Background())
if err := cmd.readReply(rd); err == nil || err.Error() != "ERR item failed" {
t.Fatalf("readReply err = %v, want ERR item failed", err)
}
if got := cmd.val; len(got) != 3 || !got[0] || got[1] || !got[2] {
t.Fatalf("unexpected val: %+v", got)
}
assertNextReplyInt(t, rd, 999)
})
t.Run("TruncatedReply", func(t *testing.T) {
rd := proto.NewReader(strings.NewReader("*3\r\n$1\r\na\r\n-ERR item failed\r\n"))
cmd := NewStringSliceCmd(context.Background())
err := cmd.readReply(rd)
if err == nil || isRedisError(err) {
t.Fatalf("readReply err = %v, want the read error", err)
}
})
}
func assertNextReplyInt(t *testing.T, rd *proto.Reader, want int64) {
t.Helper()
got, err := rd.ReadInt()