@@ -13,10 +13,11 @@ type ClusterClient struct {
13
13
14
14
addrs []string
15
15
slots [][]string
16
- slotsMx sync.RWMutex // protects slots & addrs cache
16
+ slotsMx sync.RWMutex // Protects slots and addrs.
17
17
18
18
clients map [string ]* Client
19
- clientsMx sync.RWMutex
19
+ closed bool
20
+ clientsMx sync.RWMutex // Protects clients and closed.
20
21
21
22
opt * ClusterOptions
22
23
@@ -35,20 +36,27 @@ func NewClusterClient(opt *ClusterOptions) *ClusterClient {
35
36
}
36
37
client .commandable .process = client .process
37
38
client .reloadIfDue ()
38
- go client .reaper (time . NewTicker ( 5 * time . Minute ) )
39
+ go client .reaper ()
39
40
return client
40
41
}
41
42
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.
43
47
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
46
55
c .resetClients ()
56
+ c .setSlots (nil )
47
57
return nil
48
58
}
49
59
50
- // ------------------------------------------------------------------------
51
-
52
60
// getClient returns a Client for a given address.
53
61
func (c * ClusterClient ) getClient (addr string ) (* Client , error ) {
54
62
if addr == "" {
@@ -64,6 +72,11 @@ func (c *ClusterClient) getClient(addr string) (*Client, error) {
64
72
c .clientsMx .RUnlock ()
65
73
66
74
c .clientsMx .Lock ()
75
+ if c .closed {
76
+ c .clientsMx .Unlock ()
77
+ return nil , errClosed
78
+ }
79
+
67
80
client , ok = c .clients [addr ]
68
81
if ! ok {
69
82
opt := c .opt .clientOptions ()
@@ -83,7 +96,7 @@ func (c *ClusterClient) slotAddrs(slot int) []string {
83
96
return addrs
84
97
}
85
98
86
- // randomClient returns a Client for the first live node.
99
+ // randomClient returns a Client for the first pingable node.
87
100
func (c * ClusterClient ) randomClient () (client * Client , err error ) {
88
101
for i := 0 ; i < 10 ; i ++ {
89
102
n := rand .Intn (len (c .addrs ))
@@ -165,14 +178,12 @@ func (c *ClusterClient) process(cmd Cmder) {
165
178
166
179
// Closes all clients and returns last error if there are any.
167
180
func (c * ClusterClient ) resetClients () (err error ) {
168
- c .clientsMx .Lock ()
169
181
for addr , client := range c .clients {
170
182
if e := client .Close (); e != nil {
171
183
err = e
172
184
}
173
185
delete (c .clients , addr )
174
186
}
175
- c .clientsMx .Unlock ()
176
187
return err
177
188
}
178
189
@@ -229,16 +240,28 @@ func (c *ClusterClient) scheduleReload() {
229
240
}
230
241
231
242
// 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 ()
233
246
for _ = range ticker .C {
247
+ c .clientsMx .RLock ()
248
+
249
+ if c .closed {
250
+ c .clientsMx .RUnlock ()
251
+ break
252
+ }
253
+
234
254
for _ , client := range c .clients {
235
255
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.
238
259
if cn := pool .First (); cn != nil {
239
260
pool .Put (cn )
240
261
}
241
262
}
263
+
264
+ c .clientsMx .RUnlock ()
242
265
}
243
266
}
244
267
0 commit comments