Skip to content

Commit f7a1636

Browse files
committed
Merge pull request #95 from go-redis/fix/close-client-and-reaper
Implement Close and fix reaper goroutine leak.
2 parents 593f01f + 7da9958 commit f7a1636

File tree

2 files changed

+38
-14
lines changed

2 files changed

+38
-14
lines changed

cluster.go

Lines changed: 37 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ type ClusterClient struct {
1313

1414
addrs []string
1515
slots [][]string
16-
slotsMx sync.RWMutex // protects slots & addrs cache
16+
slotsMx sync.RWMutex // Protects slots and addrs.
1717

1818
clients map[string]*Client
19-
clientsMx sync.RWMutex
19+
closed bool
20+
clientsMx sync.RWMutex // Protects clients and closed.
2021

2122
opt *ClusterOptions
2223

@@ -35,20 +36,27 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
3536
}
3637
client.commandable.process = client.process
3738
client.reloadIfDue()
38-
go client.reaper(time.NewTicker(5 * time.Minute))
39+
go client.reaper()
3940
return client
4041
}
4142

42-
// Close closes the cluster client.
43+
// Close closes the cluster client, releasing any open resources.
44+
//
45+
// It is rare to Close a Client, as the Client is meant to be
46+
// long-lived and shared between many goroutines.
4347
func (c *ClusterClient) Close() error {
44-
// TODO: close should make client unusable
45-
c.setSlots(nil)
48+
defer c.clientsMx.Unlock()
49+
c.clientsMx.Lock()
50+
51+
if c.closed {
52+
return nil
53+
}
54+
c.closed = true
4655
c.resetClients()
56+
c.setSlots(nil)
4757
return nil
4858
}
4959

50-
// ------------------------------------------------------------------------
51-
5260
// getClient returns a Client for a given address.
5361
func (c *ClusterClient) getClient(addr string) (*Client, error) {
5462
if addr == "" {
@@ -64,6 +72,11 @@ func (c *ClusterClient) getClient(addr string) (*Client, error) {
6472
c.clientsMx.RUnlock()
6573

6674
c.clientsMx.Lock()
75+
if c.closed {
76+
c.clientsMx.Unlock()
77+
return nil, errClosed
78+
}
79+
6780
client, ok = c.clients[addr]
6881
if !ok {
6982
opt := c.opt.clientOptions()
@@ -83,7 +96,7 @@ func (c *ClusterClient) slotAddrs(slot int) []string {
8396
return addrs
8497
}
8598

86-
// randomClient returns a Client for the first live node.
99+
// randomClient returns a Client for the first pingable node.
87100
func (c *ClusterClient) randomClient() (client *Client, err error) {
88101
for i := 0; i < 10; i++ {
89102
n := rand.Intn(len(c.addrs))
@@ -165,14 +178,12 @@ func (c *ClusterClient) process(cmd Cmder) {
165178

166179
// Closes all clients and returns last error if there are any.
167180
func (c *ClusterClient) resetClients() (err error) {
168-
c.clientsMx.Lock()
169181
for addr, client := range c.clients {
170182
if e := client.Close(); e != nil {
171183
err = e
172184
}
173185
delete(c.clients, addr)
174186
}
175-
c.clientsMx.Unlock()
176187
return err
177188
}
178189

@@ -229,16 +240,28 @@ func (c *ClusterClient) scheduleReload() {
229240
}
230241

231242
// reaper closes idle connections to the cluster.
232-
func (c *ClusterClient) reaper(ticker *time.Ticker) {
243+
func (c *ClusterClient) reaper() {
244+
ticker := time.NewTicker(time.Minute)
245+
defer ticker.Stop()
233246
for _ = range ticker.C {
247+
c.clientsMx.RLock()
248+
249+
if c.closed {
250+
c.clientsMx.RUnlock()
251+
break
252+
}
253+
234254
for _, client := range c.clients {
235255
pool := client.connPool
236-
// pool.First removes idle connections from the pool for us. So
237-
// just put returned connection back.
256+
// pool.First removes idle connections from the pool and
257+
// returns first non-idle connection. So just put returned
258+
// connection back.
238259
if cn := pool.First(); cn != nil {
239260
pool.Put(cn)
240261
}
241262
}
263+
264+
c.clientsMx.RUnlock()
242265
}
243266
}
244267

cluster_client_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ var _ = Describe("ClusterClient", func() {
8383
Expect(subject.slots[8191]).To(BeEmpty())
8484
Expect(subject.slots[8192]).To(BeEmpty())
8585
Expect(subject.slots[16383]).To(BeEmpty())
86+
Expect(subject.Ping().Err().Error()).To(Equal("redis: client is closed"))
8687
})
8788

8889
It("should check if reload is due", func() {

0 commit comments

Comments
 (0)