Skip to content

Commit 5465428

Browse files
authored
Merge pull request #675 from zltl/patch-2
Fix connection leak when write failed but dial success.
2 parents 49d6e7c + d91707b commit 5465428

File tree

3 files changed

+44
-4
lines changed

3 files changed

+44
-4
lines changed

โ€Žclient/circuit_breaker.goโ€Ž

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@ var (
1313

1414
// ConsecCircuitBreaker is window sliding CircuitBreaker with failure threshold.
1515
type ConsecCircuitBreaker struct {
16-
lastFailureTime time.Time
16+
// time.Time is a compund type, split into second and nano for using atomic.
17+
lastFailureTimeSecond int64
18+
lastFailureTimeNano int32
19+
1720
failures uint64
1821
failureThreshold uint64
1922
window time.Duration
@@ -64,7 +67,8 @@ func (cb *ConsecCircuitBreaker) Call(fn func() error, d time.Duration) error {
6467
}
6568

6669
func (cb *ConsecCircuitBreaker) ready() bool {
67-
if time.Since(cb.lastFailureTime) > cb.window {
70+
lastFailureTime := cb.loadLastFailureTime()
71+
if time.Since(lastFailureTime) > cb.window {
6872
cb.reset()
6973
return true
7074
}
@@ -78,7 +82,7 @@ func (cb *ConsecCircuitBreaker) success() {
7882
}
7983
func (cb *ConsecCircuitBreaker) fail() {
8084
atomic.AddUint64(&cb.failures, 1)
81-
cb.lastFailureTime = time.Now()
85+
cb.updateLastFailureTime(time.Now())
8286
}
8387

8488
func (cb *ConsecCircuitBreaker) Success() {
@@ -94,5 +98,15 @@ func (cb *ConsecCircuitBreaker) Ready() bool {
9498

9599
func (cb *ConsecCircuitBreaker) reset() {
96100
atomic.StoreUint64(&cb.failures, 0)
97-
cb.lastFailureTime = time.Now()
101+
cb.updateLastFailureTime(time.Now())
102+
}
103+
104+
func (cb *ConsecCircuitBreaker) updateLastFailureTime(cur time.Time) {
105+
atomic.StoreInt64(&cb.lastFailureTimeSecond, cur.Unix())
106+
atomic.StoreInt32(&cb.lastFailureTimeNano, int32(cur.UnixNano()))
107+
}
108+
func (cb *ConsecCircuitBreaker) loadLastFailureTime() time.Time {
109+
nano := atomic.LoadInt32(&cb.lastFailureTimeNano)
110+
second := atomic.LoadInt64(&cb.lastFailureTimeSecond)
111+
return time.Unix(second, int64(nano))
98112
}

โ€Žclient/circuit_breaker_test.goโ€Ž

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package client
22

33
import (
44
"errors"
5+
"math/rand"
56
"testing"
67
"time"
78
)
@@ -51,3 +52,24 @@ func TestConsecCircuitBreaker(t *testing.T) {
5152
}
5253

5354
}
55+
56+
func TestCircuitBreakerRace(t *testing.T) {
57+
cb := NewConsecCircuitBreaker(2, 50*time.Millisecond)
58+
routines := 100
59+
loop := 100000
60+
61+
fn := func() error {
62+
if rand.Intn(2) == 1 {
63+
return nil
64+
}
65+
return errors.New("test error")
66+
}
67+
68+
for r := 0; r < routines; r++ {
69+
go func() {
70+
for i := 0; i < loop; i++ {
71+
cb.Call(fn, 100*time.Millisecond)
72+
}
73+
}()
74+
}
75+
}

โ€Žclient/connection.goโ€Ž

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,10 @@ func newDirectHTTPConn(c *Client, network, address string) (net.Conn, error) {
139139

140140
_, err = io.WriteString(conn, "CONNECT "+path+" HTTP/1.0\n\n")
141141
if err != nil {
142+
// Dial() success but Write() failed here, close the successfully
143+
// created conn before return.
144+
conn.Close()
145+
142146
log.Errorf("failed to make CONNECT: %v", err)
143147
return nil, err
144148
}

0 commit comments

Comments
ย (0)