Handle RESP strings longer than bufio.Reader size
diff --git a/redis/conn.go b/redis/conn.go
index 5aa0f32..d281bcb 100644
--- a/redis/conn.go
+++ b/redis/conn.go
@@ -427,10 +427,21 @@
return fmt.Sprintf("redigo: %s (possible server error or unsupported concurrent read by application)", string(pe))
}
+// readLine reads a line of input from the RESP stream.
func (c *conn) readLine() ([]byte, error) {
+ // To avoid allocations, attempt to read the line using ReadSlice. This
+ // call typically succeeds. The known case where the call fails is when
+ // reading the output from the MONITOR command.
p, err := c.br.ReadSlice('\n')
if err == bufio.ErrBufferFull {
- return nil, protocolError("long response line")
+ // The line does not fit in the bufio.Reader's buffer. Fall back to
+ // allocating a buffer for the line.
+ buf := append([]byte{}, p...)
+ for err == bufio.ErrBufferFull {
+ p, err = c.br.ReadSlice('\n')
+ buf = append(buf, p...)
+ }
+ p = buf
}
if err != nil {
return nil, err
diff --git a/redis/conn_test.go b/redis/conn_test.go
index 63a4c23..779bdfd 100644
--- a/redis/conn_test.go
+++ b/redis/conn_test.go
@@ -170,6 +170,10 @@
"PONG",
},
{
+ "+OK\n\n", // no \r
+ errorSentinel,
+ },
+ {
"@OK\r\n",
errorSentinel,
},
@@ -207,6 +211,11 @@
},
{
+ // "" is not a valid length
+ "$\r\nfoobar\r\n",
+ errorSentinel,
+ },
+ {
// "x" is not a valid length
"$x\r\nfoobar\r\n",
errorSentinel,
@@ -217,6 +226,11 @@
errorSentinel,
},
{
+ // "" is not a valid integer
+ ":\r\n",
+ errorSentinel,
+ },
+ {
// "x" is not a valid integer
":x\r\n",
errorSentinel,
@@ -258,6 +272,30 @@
}
}
+func TestReadString(t *testing.T) {
+ // n is value of bufio.defaultBufSize
+ const n = 4096
+
+ // Test read string lengths near bufio.Reader buffer boundaries.
+ testRanges := [][2]int{{0, 64}, {n - 64, n + 64}, {2*n - 64, 2*n + 64}}
+
+ p := make([]byte, 2*n+64)
+ for i := range p {
+ p[i] = byte('a' + i%26)
+ }
+ s := string(p)
+
+ for _, r := range testRanges {
+ for i := r[0]; i < r[1]; i++ {
+ c, _ := redis.Dial("", "", dialTestConn("+"+s[:i]+"\r\n", nil))
+ actual, err := c.Receive()
+ if err != nil || actual != s[:i] {
+ t.Fatalf("Receive(string len %d) -> err=%v, equal=%v", i, err, actual != s[:i])
+ }
+ }
+ }
+}
+
var testCommands = []struct {
args []interface{}
expected interface{}