Skip to content

Ring.Pipelined() returns nil error despite DialTimeout and failed shard connection #3402

Open
@smnvdev

Description

@smnvdev

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

  1. Configure Ring with an unreachable shard address (e.g., 10.255.255.1:6379)
  2. Set DialTimeout to a short value (e.g., 1s)
  3. Call Ring.Pipelined() with any Redis commands (e.g., HSet, Expire)
  4. 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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions