@@ -42,8 +42,10 @@ type Client struct {
42
42
cbMu sync.RWMutex
43
43
callbacks map [int ]callback
44
44
45
- // Interval at which echo RPCs should occur in the background.
46
- echoInterval time.Duration
45
+ // Interval at which echo RPCs should occur in the background, and statistics
46
+ // about the echo loop.
47
+ echoInterval time.Duration
48
+ echoOK , echoFail * int64
47
49
48
50
// Track and clean up background goroutines.
49
51
cancel func ()
@@ -110,6 +112,10 @@ func New(conn net.Conn, options ...OptionFunc) (*Client, error) {
110
112
wg .Add (1 )
111
113
112
114
// If configured, send echo RPCs in the background at a fixed interval.
115
+ var echoOK , echoFail int64
116
+ client .echoOK = & echoOK
117
+ client .echoFail = & echoFail
118
+
113
119
if d := client .echoInterval ; d != 0 {
114
120
wg .Add (1 )
115
121
go func () {
@@ -136,8 +142,8 @@ func (c *Client) requestID() int {
136
142
137
143
// Close closes a Client's connection and cleans up its resources.
138
144
func (c * Client ) Close () error {
139
- err := c .c .Close ()
140
145
c .cancel ()
146
+ err := c .c .Close ()
141
147
c .wg .Wait ()
142
148
return err
143
149
}
@@ -147,9 +153,11 @@ func (c *Client) Stats() ClientStats {
147
153
var s ClientStats
148
154
149
155
c .cbMu .RLock ()
150
- defer c .cbMu .RUnlock ()
151
-
152
156
s .Callbacks .Current = len (c .callbacks )
157
+ c .cbMu .RUnlock ()
158
+
159
+ s .EchoLoop .Success = int (atomic .LoadInt64 (c .echoOK ))
160
+ s .EchoLoop .Failure = int (atomic .LoadInt64 (c .echoFail ))
153
161
154
162
return s
155
163
}
@@ -162,6 +170,13 @@ type ClientStats struct {
162
170
// for RPC responses.
163
171
Current int
164
172
}
173
+
174
+ // Statistics about the Client's internal echo RPC loop.
175
+ // Note that all counters will be zero if the echo loop is disabled.
176
+ EchoLoop struct {
177
+ // The number of successful and failed echo RPCs in the loop.
178
+ Success , Failure int
179
+ }
165
180
}
166
181
167
182
// rpc performs a single RPC request, and checks the response for errors.
@@ -237,7 +252,7 @@ func (c *Client) listen() {
237
252
res , err := c .c .Receive ()
238
253
if err != nil {
239
254
// EOF or closed connection means time to stop serving.
240
- if err == io .EOF || strings . Contains (err . Error (), "use of closed network" ) {
255
+ if err == io .EOF || isClosedNetwork (err ) {
241
256
return
242
257
}
243
258
@@ -268,16 +283,34 @@ func (c *Client) echoLoop(ctx context.Context, d time.Duration) {
268
283
defer t .Stop ()
269
284
270
285
for {
286
+ // If context is canceled, we should exit this loop. If a tick is fired
287
+ // and the context was already canceled, we exit there as well.
271
288
select {
272
289
case <- ctx .Done ():
273
290
return
274
291
case <- t .C :
292
+ if err := ctx .Err (); err != nil {
293
+ return
294
+ }
275
295
}
276
296
277
- // No feasible way to handle errors here. In the future, it may be
278
- // possible to do something like re-establishing the connection.
279
- // TOOD(mdlayher): improve error handling for echo loop.
280
- _ = c .Echo (ctx )
297
+ // For the time being, we will track metrics about the number of successes
298
+ // and failures while sending echo RPCs.
299
+ // TODO(mdlayher): improve error handling for echo loop.
300
+ if err := c .Echo (ctx ); err != nil {
301
+ if isClosedNetwork (err ) {
302
+ // Our socket was closed, which means the context should be canceled
303
+ // and we should terminate on the next loop. No need to increment
304
+ // errors counter.
305
+ continue
306
+ }
307
+
308
+ // Count other errors as failures.
309
+ atomic .AddInt64 (c .echoFail , 1 )
310
+ continue
311
+ }
312
+
313
+ atomic .AddInt64 (c .echoOK , 1 )
281
314
}
282
315
}
283
316
@@ -327,6 +360,16 @@ func (c *Client) doCallback(id int, res rpcResponse) {
327
360
delete (c .callbacks , id )
328
361
}
329
362
363
+ // isClosedNetwork checks for errors caused by a closed network connection.
364
+ func isClosedNetwork (err error ) bool {
365
+ if err == nil {
366
+ return false
367
+ }
368
+
369
+ // Not an awesome solution, but see: https://github.com/golang/go/issues/4373.
370
+ return strings .Contains (err .Error (), "use of closed network connection" )
371
+ }
372
+
330
373
func panicf (format string , a ... interface {}) {
331
374
panic (fmt .Sprintf (format , a ... ))
332
375
}
0 commit comments