Skip to content

Commit 080e051

Browse files
LINKIWIofekshenawa
andauthored
Eliminate redundant dial mutex causing unbounded connection queue contention (#3088)
* Eliminate redundant dial mutex causing unbounded connection queue contention * Dialer connection timeouts unit test --------- Co-authored-by: ofekshenawa <[email protected]>
1 parent 930d904 commit 080e051

File tree

2 files changed

+65
-2
lines changed

2 files changed

+65
-2
lines changed

redis.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,6 @@ func (hs *hooksMixin) withProcessPipelineHook(
176176
}
177177

178178
func (hs *hooksMixin) dialHook(ctx context.Context, network, addr string) (net.Conn, error) {
179-
hs.hooksMu.Lock()
180-
defer hs.hooksMu.Unlock()
181179
return hs.current.dial(ctx, network, addr)
182180
}
183181

redis_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"net"
9+
"sync"
910
"testing"
1011
"time"
1112

@@ -633,3 +634,67 @@ var _ = Describe("Hook with MinIdleConns", func() {
633634
}))
634635
})
635636
})
637+
638+
var _ = Describe("Dialer connection timeouts", func() {
639+
var client *redis.Client
640+
641+
const dialSimulatedDelay = 1 * time.Second
642+
643+
BeforeEach(func() {
644+
options := redisOptions()
645+
options.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
646+
// Simulated slow dialer.
647+
// Note that the following sleep is deliberately not context-aware.
648+
time.Sleep(dialSimulatedDelay)
649+
return net.Dial("tcp", options.Addr)
650+
}
651+
options.MinIdleConns = 1
652+
client = redis.NewClient(options)
653+
})
654+
655+
AfterEach(func() {
656+
err := client.Close()
657+
Expect(err).NotTo(HaveOccurred())
658+
})
659+
660+
It("does not contend on connection dial for concurrent commands", func() {
661+
var wg sync.WaitGroup
662+
663+
const concurrency = 10
664+
665+
durations := make(chan time.Duration, concurrency)
666+
errs := make(chan error, concurrency)
667+
668+
start := time.Now()
669+
wg.Add(concurrency)
670+
671+
for i := 0; i < concurrency; i++ {
672+
go func() {
673+
defer wg.Done()
674+
675+
start := time.Now()
676+
err := client.Ping(ctx).Err()
677+
durations <- time.Since(start)
678+
errs <- err
679+
}()
680+
}
681+
682+
wg.Wait()
683+
close(durations)
684+
close(errs)
685+
686+
// All commands should eventually succeed, after acquiring a connection.
687+
for err := range errs {
688+
Expect(err).NotTo(HaveOccurred())
689+
}
690+
691+
// Each individual command should complete within the simulated dial duration bound.
692+
for duration := range durations {
693+
Expect(duration).To(BeNumerically("<", 2*dialSimulatedDelay))
694+
}
695+
696+
// Due to concurrent execution, the entire test suite should also complete within
697+
// the same dial duration bound applied for individual commands.
698+
Expect(time.Since(start)).To(BeNumerically("<", 2*dialSimulatedDelay))
699+
})
700+
})

0 commit comments

Comments
 (0)