@@ -65,10 +65,15 @@ func Debug(ll *log.Logger) OptionFunc {
65
65
}
66
66
67
67
// EchoInterval specifies an interval at which the Client will send
68
- // echo RPCs to an OVSDB server to keep the connection alive. If this
69
- // option is not used, the Client will not send any echo RPCs on its own.
68
+ // echo RPCs to an OVSDB server to keep the connection alive. Note that the
69
+ // OVSDB server may also send its own echo RPCs to the Client, and the Client
70
+ // will always reply to those on behalf of the user.
70
71
//
71
- // Specify a duration of 0 to disable sending background echo RPCs.
72
+ // If this option is not used, the Client will only send echo RPCs when the
73
+ // server sends an echo RPC to it.
74
+ //
75
+ // Specify a duration of 0 to disable sending background echo RPCs at a
76
+ // fixed interval.
72
77
func EchoInterval (d time.Duration ) OptionFunc {
73
78
return func (c * Client ) error {
74
79
c .echoInterval = d
@@ -105,30 +110,40 @@ func New(conn net.Conn, options ...OptionFunc) (*Client, error) {
105
110
// Set up callbacks.
106
111
client .callbacks = make (map [string ]callback )
107
112
113
+ // Set up echo loop statistics.
114
+ var echoOK , echoFail int64
115
+ client .echoOK = & echoOK
116
+ client .echoFail = & echoFail
117
+
118
+ // Coordinates the sending of echo messages among multiple goroutines.
119
+ echoC := make (chan struct {})
120
+
108
121
// Start up any background routines, and enable canceling them via context.
109
122
ctx , cancel := context .WithCancel (context .Background ())
110
123
client .cancel = cancel
111
124
112
125
var wg sync.WaitGroup
113
- wg .Add (1 )
114
-
115
- // If configured, send echo RPCs in the background at a fixed interval.
116
- var echoOK , echoFail int64
117
- client .echoOK = & echoOK
118
- client .echoFail = & echoFail
126
+ wg .Add (2 )
119
127
128
+ // If configured, trigger echo RPCs in the background at a fixed interval.
120
129
if d := client .echoInterval ; d != 0 {
121
130
wg .Add (1 )
122
131
go func () {
123
132
defer wg .Done ()
124
- client .echoLoop (ctx , d )
133
+ client .echoTicker (ctx , d , echoC )
125
134
}()
126
135
}
127
136
137
+ // Send echo RPCs when triggered by channel.
138
+ go func () {
139
+ defer wg .Done ()
140
+ client .echoLoop (ctx , echoC )
141
+ }()
142
+
128
143
// Handle all incoming RPC responses and notifications.
129
144
go func () {
130
145
defer wg .Done ()
131
- client .listen ()
146
+ client .listen (ctx , echoC )
132
147
}()
133
148
134
149
client .wg = & wg
@@ -175,7 +190,6 @@ type ClientStats struct {
175
190
}
176
191
177
192
// Statistics about the Client's internal echo RPC loop.
178
- // Note that all counters will be zero if the echo loop is disabled.
179
193
EchoLoop struct {
180
194
// The number of successful and failed echo RPCs in the loop.
181
195
Success , Failure int
@@ -250,7 +264,7 @@ func (c *Client) rpc(ctx context.Context, method string, out interface{}, args .
250
264
251
265
// listen starts an RPC receive loop that can return RPC results to
252
266
// clients via a callback.
253
- func (c * Client ) listen () {
267
+ func (c * Client ) listen (ctx context. Context , echoC chan <- struct {} ) {
254
268
for {
255
269
res , err := c .c .Receive ()
256
270
if err != nil {
@@ -263,7 +277,20 @@ func (c *Client) listen() {
263
277
continue
264
278
}
265
279
266
- // TODO(mdlayher): deal with RPC notifications.
280
+ // Handle any JSON-RPC notifications.
281
+ // TODO(mdlayher): deal with other RPC notifications.
282
+ switch res .Method {
283
+ case "echo" :
284
+ // OVSDB server wants us to send an echo to it, but will also send
285
+ // us a response to that echo. Since this goroutine is the one that
286
+ // needs to receive that response and issue the callback for it, we
287
+ // ask the echo loop goroutine to send an echo on our behalf.
288
+ select {
289
+ case <- ctx .Done ():
290
+ case echoC <- struct {}{}:
291
+ }
292
+ continue
293
+ }
267
294
268
295
// Handle any JSON-RPC top-level errors.
269
296
if err := res .Err (); err != nil {
@@ -280,13 +307,14 @@ func (c *Client) listen() {
280
307
}
281
308
}
282
309
283
- // echoLoop starts a loop that sends echo RPCs at the interval defined by d.
284
- func (c * Client ) echoLoop (ctx context.Context , d time.Duration ) {
310
+ // echoTicker starts a loop that triggers echo RPCs via channel at a fixed
311
+ // time interval.
312
+ func (c * Client ) echoTicker (ctx context.Context , d time.Duration , echoC chan <- struct {}) {
285
313
t := time .NewTicker (d )
286
314
defer t .Stop ()
287
315
288
316
for {
289
- // If context is canceled, we should exit this loop. If a tick is fired
317
+ // If context is canceled, we should exit this loop. If a request is fired
290
318
// and the context was already canceled, we exit there as well.
291
319
select {
292
320
case <- ctx .Done ():
@@ -297,6 +325,30 @@ func (c *Client) echoLoop(ctx context.Context, d time.Duration) {
297
325
}
298
326
}
299
327
328
+ // Allow producer to stop if context closed instead of blocking if
329
+ // the consumer is stopped.
330
+ select {
331
+ case <- ctx .Done ():
332
+ return
333
+ case echoC <- struct {}{}:
334
+ }
335
+ }
336
+ }
337
+
338
+ // echoLoop starts a loop that sends echo RPCs when requested via channel.
339
+ func (c * Client ) echoLoop (ctx context.Context , echoC <- chan struct {}) {
340
+ for {
341
+ // If context is canceled, we should exit this loop. If a request is fired
342
+ // and the context was already canceled, we exit there as well.
343
+ select {
344
+ case <- ctx .Done ():
345
+ return
346
+ case <- echoC :
347
+ if err := ctx .Err (); err != nil {
348
+ return
349
+ }
350
+ }
351
+
300
352
// For the time being, we will track metrics about the number of successes
301
353
// and failures while sending echo RPCs.
302
354
// TODO(mdlayher): improve error handling for echo loop.
0 commit comments