mirror of
https://github.com/hanzokv/go.git
synced 2026-09-24 00:31:51 +00:00
Co-authored-by: Vladislav Isakov <[email protected]>
This commit is contained in:
co-authored by
Vladislav Isakov
parent
ad7fabb336
commit
c615d30b7d
@@ -91,6 +91,14 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Dial errors mean TCP connection was never established — safe to retry even
|
||||
// when wrapped inside context.DeadlineExceeded (from DialTimeout context).
|
||||
// Must be checked before the context error check below.
|
||||
var opErr *net.OpError
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check for context errors (works with wrapped errors)
|
||||
if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
|
||||
return false
|
||||
@@ -105,12 +113,6 @@ func shouldRetry(err error, retryTimeout bool) bool {
|
||||
// Check for timeout errors (works with wrapped errors)
|
||||
if isTimeout, hasTimeoutFlag := isTimeoutError(err); isTimeout {
|
||||
if hasTimeoutFlag {
|
||||
// A dial error means the TCP connection was never established and the
|
||||
// command was never sent to the server, so retry is always safe
|
||||
var opErr *net.OpError
|
||||
if errors.As(err, &opErr) && opErr.Op == "dial" {
|
||||
return true
|
||||
}
|
||||
return retryTimeout
|
||||
}
|
||||
return true
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -638,6 +639,59 @@ func TestContextErrorWrapping(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestDialTimeoutShouldRetry tests that dial errors wrapped in context.DeadlineExceeded
|
||||
// (as produced by pool.go's context.WithTimeout(ctx, DialTimeout)) are retried.
|
||||
func TestDialTimeoutShouldRetry(t *testing.T) {
|
||||
makeDialOpError := func(inner error) *net.OpError {
|
||||
return &net.OpError{Op: "dial", Net: "tcp", Err: inner}
|
||||
}
|
||||
|
||||
t.Run("bare dial OpError retries", func(t *testing.T) {
|
||||
err := makeDialOpError(&net.AddrError{Err: "connection refused", Addr: "127.0.0.1:6379"})
|
||||
if !redis.ShouldRetry(err, false) {
|
||||
t.Errorf("bare dial OpError should retry")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("dial OpError wrapped in context.DeadlineExceeded retries", func(t *testing.T) {
|
||||
// Simulates pool.go wraps net.OpError as DeadlineExceeded.
|
||||
dialErr := makeDialOpError(context.DeadlineExceeded)
|
||||
wrappedErr := fmt.Errorf("dial tcp: %w", dialErr)
|
||||
if !redis.ShouldRetry(wrappedErr, false) {
|
||||
t.Errorf("dial OpError wrapped in DeadlineExceeded should retry, got false")
|
||||
}
|
||||
if !redis.ShouldRetry(wrappedErr, true) {
|
||||
t.Errorf("dial OpError wrapped in DeadlineExceeded should retry, got false")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("plain context.DeadlineExceeded does not retry", func(t *testing.T) {
|
||||
err := fmt.Errorf("op failed: %w", context.DeadlineExceeded)
|
||||
if redis.ShouldRetry(err, false) {
|
||||
t.Errorf("plain DeadlineExceeded should not retry")
|
||||
}
|
||||
if redis.ShouldRetry(err, true) {
|
||||
t.Errorf("plain DeadlineExceeded should not retry even with retryTimeout=true")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("plain context.Canceled does not retry", func(t *testing.T) {
|
||||
err := fmt.Errorf("op failed: %w", context.Canceled)
|
||||
if redis.ShouldRetry(err, false) {
|
||||
t.Errorf("plain context.Canceled should not retry")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("non-dial OpError does not bypass DeadlineExceeded check", func(t *testing.T) {
|
||||
// Op="read" wrapped in DeadlineExceeded should NOT retry (user context expired)
|
||||
readErr := &net.OpError{Op: "read", Net: "tcp", Err: context.DeadlineExceeded}
|
||||
wrappedErr := fmt.Errorf("read: %w", readErr)
|
||||
if redis.ShouldRetry(wrappedErr, false) {
|
||||
t.Errorf("non-dial OpError with DeadlineExceeded should not retry")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestIOErrorWrapping tests that io errors work correctly when wrapped
|
||||
func TestIOErrorWrapping(t *testing.T) {
|
||||
t.Run("Wrapped io.EOF", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user