Skip to content

Commit 75ceb98

Browse files
authored
Merge pull request #587 from go-redis/fix/pool-dial-errors
Gracefully handle situation when Redis Server is down
2 parents c815953 + 9cf5f25 commit 75ceb98

File tree

4 files changed

+115
-47
lines changed

4 files changed

+115
-47
lines changed

internal/pool/bench_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import (
88
)
99

1010
func benchmarkPoolGetPut(b *testing.B, poolSize int) {
11-
connPool := pool.NewConnPool(dummyDialer, poolSize, time.Second, time.Hour, time.Hour)
11+
connPool := pool.NewConnPool(&pool.Options{
12+
Dialer: dummyDialer,
13+
PoolSize: poolSize,
14+
PoolTimeout: time.Second,
15+
IdleTimeout: time.Hour,
16+
IdleCheckFrequency: time.Hour,
17+
})
1218

1319
b.ResetTimer()
1420

@@ -38,7 +44,13 @@ func BenchmarkPoolGetPut1000Conns(b *testing.B) {
3844
}
3945

4046
func benchmarkPoolGetRemove(b *testing.B, poolSize int) {
41-
connPool := pool.NewConnPool(dummyDialer, poolSize, time.Second, time.Hour, time.Hour)
47+
connPool := pool.NewConnPool(&pool.Options{
48+
Dialer: dummyDialer,
49+
PoolSize: poolSize,
50+
PoolTimeout: time.Second,
51+
IdleTimeout: time.Hour,
52+
IdleCheckFrequency: time.Hour,
53+
})
4254

4355
b.ResetTimer()
4456

internal/pool/pool.go

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -46,14 +46,21 @@ type Pooler interface {
4646
Close() error
4747
}
4848

49-
type dialer func() (net.Conn, error)
49+
type Options struct {
50+
Dialer func() (net.Conn, error)
51+
OnClose func(*Conn) error
52+
53+
PoolSize int
54+
PoolTimeout time.Duration
55+
IdleTimeout time.Duration
56+
IdleCheckFrequency time.Duration
57+
}
5058

5159
type ConnPool struct {
52-
dial dialer
53-
OnClose func(*Conn) error
60+
opt *Options
5461

55-
poolTimeout time.Duration
56-
idleTimeout time.Duration
62+
dialErrorsNum uint32 // atomic
63+
_lastDialError atomic.Value
5764

5865
queue chan struct{}
5966

@@ -65,24 +72,21 @@ type ConnPool struct {
6572

6673
stats Stats
6774

68-
_closed int32 // atomic
75+
_closed uint32 // atomic
6976
}
7077

7178
var _ Pooler = (*ConnPool)(nil)
7279

73-
func NewConnPool(dial dialer, poolSize int, poolTimeout, idleTimeout, idleCheckFrequency time.Duration) *ConnPool {
80+
func NewConnPool(opt *Options) *ConnPool {
7481
p := &ConnPool{
75-
dial: dial,
76-
77-
poolTimeout: poolTimeout,
78-
idleTimeout: idleTimeout,
82+
opt: opt,
7983

80-
queue: make(chan struct{}, poolSize),
81-
conns: make([]*Conn, 0, poolSize),
82-
freeConns: make([]*Conn, 0, poolSize),
84+
queue: make(chan struct{}, opt.PoolSize),
85+
conns: make([]*Conn, 0, opt.PoolSize),
86+
freeConns: make([]*Conn, 0, opt.PoolSize),
8387
}
84-
if idleTimeout > 0 && idleCheckFrequency > 0 {
85-
go p.reaper(idleCheckFrequency)
88+
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
89+
go p.reaper(opt.IdleCheckFrequency)
8690
}
8791
return p
8892
}
@@ -92,8 +96,16 @@ func (p *ConnPool) NewConn() (*Conn, error) {
9296
return nil, ErrClosed
9397
}
9498

95-
netConn, err := p.dial()
99+
if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) {
100+
return nil, p.lastDialError()
101+
}
102+
103+
netConn, err := p.opt.Dialer()
96104
if err != nil {
105+
p.setLastDialError(err)
106+
if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) {
107+
go p.tryDial()
108+
}
97109
return nil, err
98110
}
99111

@@ -105,12 +117,35 @@ func (p *ConnPool) NewConn() (*Conn, error) {
105117
return cn, nil
106118
}
107119

120+
func (p *ConnPool) tryDial() {
121+
for {
122+
conn, err := p.opt.Dialer()
123+
if err != nil {
124+
p.setLastDialError(err)
125+
time.Sleep(time.Second)
126+
continue
127+
}
128+
129+
atomic.StoreUint32(&p.dialErrorsNum, 0)
130+
_ = conn.Close()
131+
return
132+
}
133+
}
134+
135+
func (p *ConnPool) setLastDialError(err error) {
136+
p._lastDialError.Store(err)
137+
}
138+
139+
func (p *ConnPool) lastDialError() error {
140+
return p._lastDialError.Load().(error)
141+
}
142+
108143
func (p *ConnPool) PopFree() *Conn {
109144
select {
110145
case p.queue <- struct{}{}:
111146
default:
112147
timer := timers.Get().(*time.Timer)
113-
timer.Reset(p.poolTimeout)
148+
timer.Reset(p.opt.PoolTimeout)
114149

115150
select {
116151
case p.queue <- struct{}{}:
@@ -158,7 +193,7 @@ func (p *ConnPool) Get() (*Conn, bool, error) {
158193
case p.queue <- struct{}{}:
159194
default:
160195
timer := timers.Get().(*time.Timer)
161-
timer.Reset(p.poolTimeout)
196+
timer.Reset(p.opt.PoolTimeout)
162197

163198
select {
164199
case p.queue <- struct{}{}:
@@ -182,7 +217,7 @@ func (p *ConnPool) Get() (*Conn, bool, error) {
182217
break
183218
}
184219

185-
if cn.IsStale(p.idleTimeout) {
220+
if cn.IsStale(p.opt.IdleTimeout) {
186221
p.CloseConn(cn)
187222
continue
188223
}
@@ -232,8 +267,8 @@ func (p *ConnPool) CloseConn(cn *Conn) error {
232267
}
233268

234269
func (p *ConnPool) closeConn(cn *Conn) error {
235-
if p.OnClose != nil {
236-
_ = p.OnClose(cn)
270+
if p.opt.OnClose != nil {
271+
_ = p.opt.OnClose(cn)
237272
}
238273
return cn.Close()
239274
}
@@ -265,11 +300,11 @@ func (p *ConnPool) Stats() *Stats {
265300
}
266301

267302
func (p *ConnPool) closed() bool {
268-
return atomic.LoadInt32(&p._closed) == 1
303+
return atomic.LoadUint32(&p._closed) == 1
269304
}
270305

271306
func (p *ConnPool) Close() error {
272-
if !atomic.CompareAndSwapInt32(&p._closed, 0, 1) {
307+
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
273308
return ErrClosed
274309
}
275310

@@ -299,7 +334,7 @@ func (p *ConnPool) reapStaleConn() bool {
299334
}
300335

301336
cn := p.freeConns[0]
302-
if !cn.IsStale(p.idleTimeout) {
337+
if !cn.IsStale(p.opt.IdleTimeout) {
303338
return false
304339
}
305340

internal/pool/pool_test.go

Lines changed: 34 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,13 @@ var _ = Describe("ConnPool", func() {
1414
var connPool *pool.ConnPool
1515

1616
BeforeEach(func() {
17-
connPool = pool.NewConnPool(
18-
dummyDialer, 10, time.Hour, time.Millisecond, time.Millisecond)
17+
connPool = pool.NewConnPool(&pool.Options{
18+
Dialer: dummyDialer,
19+
PoolSize: 10,
20+
PoolTimeout: time.Hour,
21+
IdleTimeout: time.Millisecond,
22+
IdleCheckFrequency: time.Millisecond,
23+
})
1924
})
2025

2126
AfterEach(func() {
@@ -83,16 +88,21 @@ var _ = Describe("conns reaper", func() {
8388
var conns, idleConns, closedConns []*pool.Conn
8489

8590
BeforeEach(func() {
86-
connPool = pool.NewConnPool(
87-
dummyDialer, 10, time.Second, idleTimeout, time.Hour)
88-
91+
conns = nil
8992
closedConns = nil
90-
connPool.OnClose = func(cn *pool.Conn) error {
91-
closedConns = append(closedConns, cn)
92-
return nil
93-
}
9493

95-
conns = nil
94+
connPool = pool.NewConnPool(&pool.Options{
95+
Dialer: dummyDialer,
96+
PoolSize: 10,
97+
PoolTimeout: time.Second,
98+
IdleTimeout: idleTimeout,
99+
IdleCheckFrequency: time.Hour,
100+
101+
OnClose: func(cn *pool.Conn) error {
102+
closedConns = append(closedConns, cn)
103+
return nil
104+
},
105+
})
96106

97107
// add stale connections
98108
idleConns = nil
@@ -202,8 +212,13 @@ var _ = Describe("race", func() {
202212
})
203213

204214
It("does not happen on Get, Put, and Remove", func() {
205-
connPool = pool.NewConnPool(
206-
dummyDialer, 10, time.Minute, time.Millisecond, time.Millisecond)
215+
connPool = pool.NewConnPool(&pool.Options{
216+
Dialer: dummyDialer,
217+
PoolSize: 10,
218+
PoolTimeout: time.Minute,
219+
IdleTimeout: time.Millisecond,
220+
IdleCheckFrequency: time.Millisecond,
221+
})
207222

208223
perform(C, func(id int) {
209224
for i := 0; i < N; i++ {
@@ -226,7 +241,13 @@ var _ = Describe("race", func() {
226241

227242
It("does not happen on Get and PopFree", func() {
228243
connPool = pool.NewConnPool(
229-
dummyDialer, 10, time.Minute, time.Second, time.Millisecond)
244+
&pool.Options{
245+
Dialer: dummyDialer,
246+
PoolSize: 10,
247+
PoolTimeout: time.Minute,
248+
IdleTimeout: time.Second,
249+
IdleCheckFrequency: time.Millisecond,
250+
})
230251

231252
perform(C, func(id int) {
232253
for i := 0; i < N; i++ {

options.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,13 @@ func ParseURL(redisURL string) (*Options, error) {
181181
}
182182

183183
func newConnPool(opt *Options) *pool.ConnPool {
184-
return pool.NewConnPool(
185-
opt.Dialer,
186-
opt.PoolSize,
187-
opt.PoolTimeout,
188-
opt.IdleTimeout,
189-
opt.IdleCheckFrequency,
190-
)
184+
return pool.NewConnPool(&pool.Options{
185+
Dialer: opt.Dialer,
186+
PoolSize: opt.PoolSize,
187+
PoolTimeout: opt.PoolTimeout,
188+
IdleTimeout: opt.IdleTimeout,
189+
IdleCheckFrequency: opt.IdleCheckFrequency,
190+
})
191191
}
192192

193193
// PoolStats contains pool state information and accumulated stats.

0 commit comments

Comments
 (0)