Description
When using Ring.Pipelined()
with an unreachable Redis address and a low DialTimeout
, no error is returned, even though none of the commands are actually sent or executed. In contrast, Client.Pipelined()
correctly returns a dial tcp ... i/o timeout
error in this case.
This inconsistency can lead to the false assumption that a pipeline was successfully executed when in fact it was silently discarded.
Expected Behavior
The call to ring.Pipelined()
should return an error if:
- No shard was available due to DialTimeout
- None of the commands in the pipeline were actually executed
Returning nil
in this case gives the false impression that the operations succeeded.
Current Behavior
When all configured shard addresses are unreachable (e.g., due to a DialTimeout
), Ring.Pipelined()
returns no error, even though the pipeline is never sent or executed on any shard. This is inconsistent with Client.Pipelined()
behavior, which correctly returns an i/o timeout error
under similar conditions.
Possible Solution
Ring.Pipelined()
should return an error if it fails to send the pipeline to any shard, either due to connection issues or internal routing failure.
Steps to Reproduce
- Configure
Ring
with an unreachable shard address (e.g.,10.255.255.1:6379
) - Set
DialTimeout
to a short value (e.g.,1s
) - Call
Ring.Pipelined()
with any Redis commands (e.g.,HSet
,Expire
) - Observe that no error is returned
package main
import (
"context"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
func main() {
ring := redis.NewRing(&redis.RingOptions{
Addrs: map[string]string{"shard1": "10.255.255.1:6379"}, // unreachable IP
DialTimeout: 1 * time.Second,
})
ctx := context.Background()
// Using Pipelined to execute multiple commands atomically
_, err := ring.Pipelined(ctx, func(pipe redis.Pipeliner) error {
pipe.HSet(ctx, "key", "field", "value")
pipe.Expire(ctx, "key", time.Minute)
return nil
})
if err != nil {
fmt.Printf("pipeline error: %v\n", err)
} else {
fmt.Println("pipeline succeeded") // unexpected behavior
}
}
Context (Environment)
In some cases, transient network failures or misconfigured shard entries result in silent data loss, since Pipelined()
returns no error. This creates difficult-to-detect consistency bugs in our application.
Detailed
Ring.Pipelined()
currently swallows errors when it fails to obtain a client for a shard. Specifically, this happens in the following code block: https://github.com/redis/go-redis/blob/master/ring.go#L801-L803