MCPcopy
hub / github.com/redis/go-redis / TestDoubleFreeTurnBug

Function TestDoubleFreeTurnBug

internal/pool/double_freeturn_test.go:22–136  ·  view source on GitHub ↗

TestDoubleFreeTurnBug demonstrates the double freeTurn bug where: 1. Dial goroutine creates a connection 2. Original waiter times out 3. putIdleConn delivers connection to another waiter 4. Dial goroutine calls freeTurn() (FIRST FREE) 5. Second waiter uses connection and calls Put() 6. Put() calls f

(t *testing.T)

Source from the content-addressed store, hash-verified

20// This causes the semaphore to be released twice, allowing more concurrent
21// operations than PoolSize allows.
22func TestDoubleFreeTurnBug(t *testing.T) {
23 var dialCount atomic.Int32
24 var putCount atomic.Int32
25
26 // Slow dialer - 150ms per dial
27 slowDialer := func(ctx context.Context) (net.Conn, error) {
28 dialCount.Add(1)
29 select {
30 case <-time.After(150 * time.Millisecond):
31 server, client := net.Pipe()
32 go func() {
33 defer server.Close()
34 buf := make([]byte, 1024)
35 for {
36 _, err := server.Read(buf)
37 if err != nil {
38 return
39 }
40 }
41 }()
42 return client, nil
43 case <-ctx.Done():
44 return nil, ctx.Err()
45 }
46 }
47
48 opt := &Options{
49 Dialer: slowDialer,
50 PoolSize: 10, // Small pool to make bug easier to trigger
51 MaxConcurrentDials: 10,
52 MinIdleConns: 0,
53 PoolTimeout: 100 * time.Millisecond,
54 DialTimeout: 5 * time.Second,
55 }
56
57 connPool := NewConnPool(opt)
58 defer connPool.Close()
59
60 // Scenario:
61 // 1. Request A starts dial (100ms timeout - will timeout before dial completes)
62 // 2. Request B arrives (500ms timeout - will wait in queue)
63 // 3. Request A times out at 100ms
64 // 4. Dial completes at 150ms
65 // 5. putIdleConn delivers connection to Request B
66 // 6. Dial goroutine calls freeTurn() - FIRST FREE
67 // 7. Request B uses connection and calls Put()
68 // 8. Put() calls freeTurn() - SECOND FREE (BUG!)
69
70 var wg sync.WaitGroup
71
72 // Request A: Short timeout, will timeout before dial completes
73 wg.Add(1)
74 go func() {
75 defer wg.Done()
76 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond)
77 defer cancel()
78
79 cn, err := connPool.Get(ctx)

Callers

nothing calls this directly

Calls 11

CloseMethod · 0.95
GetMethod · 0.95
PutMethod · 0.95
NewConnPoolFunction · 0.85
WithTimeoutMethod · 0.80
WaitMethod · 0.80
AddMethod · 0.65
CloseMethod · 0.65
ErrMethod · 0.65
ReadMethod · 0.45
LoadMethod · 0.45

Tested by

no test coverage detected