@@ -37,7 +37,7 @@ type Client struct {
37
37
38
38
// Callbacks for RPC responses.
39
39
cbMu sync.RWMutex
40
- callbacks map [int ]chan rpcResponse
40
+ callbacks map [int ]callback
41
41
42
42
wg * sync.WaitGroup
43
43
}
@@ -80,7 +80,7 @@ func New(conn net.Conn, options ...OptionFunc) (*Client, error) {
80
80
client .c = jsonrpc .NewConn (conn , client .ll )
81
81
82
82
// Set up callbacks.
83
- client .callbacks = make (map [int ]chan rpcResponse )
83
+ client .callbacks = make (map [int ]callback )
84
84
85
85
// Start up any background routines.
86
86
var wg sync.WaitGroup
@@ -134,8 +134,10 @@ func (c *Client) rpc(ctx context.Context, method string, out interface{}, args .
134
134
135
135
// Add callback for this RPC ID to return results via channel.
136
136
ch := make (chan rpcResponse , 1 )
137
- defer close (ch )
138
- c .addCallback (req .ID , ch )
137
+ c .addCallback (req .ID , callback {
138
+ Ctx : ctx ,
139
+ Response : ch ,
140
+ })
139
141
140
142
if err := c .c .Send (req ); err != nil {
141
143
return err
@@ -144,9 +146,16 @@ func (c *Client) rpc(ctx context.Context, method string, out interface{}, args .
144
146
// Await RPC completion or cancelation.
145
147
select {
146
148
case <- ctx .Done ():
147
- // RPC canceled; clean up callback.
148
- return c .cancelCallback (ctx , req .ID )
149
- case res := <- ch :
149
+ // RPC canceled. Producer cleans up the callback.
150
+ return ctx .Err ()
151
+ case res , ok := <- ch :
152
+ if ! ok {
153
+ // Channel was closed by producer after a context cancelation,
154
+ // and woke up this consumer. The select statement happened
155
+ // to pick this case even though the context was canceled.
156
+ return ctx .Err ()
157
+ }
158
+
150
159
// RPC complete.
151
160
return rpcResult (res , & r )
152
161
}
@@ -184,9 +193,15 @@ func (c *Client) listen() {
184
193
}
185
194
}
186
195
187
- // addCallback registers a callback for an RPC response for the specified ID,
188
- // and accepts a channel to return the results on.
189
- func (c * Client ) addCallback (id int , ch chan rpcResponse ) {
196
+ // A callback can be used to send a message back to a caller, or
197
+ // allow the caller to cancel waiting for a message.
198
+ type callback struct {
199
+ Ctx context.Context
200
+ Response chan rpcResponse
201
+ }
202
+
203
+ // addCallback registers a callback for an RPC response for the specified ID.
204
+ func (c * Client ) addCallback (id int , cb callback ) {
190
205
c .cbMu .Lock ()
191
206
defer c .cbMu .Unlock ()
192
207
@@ -195,7 +210,7 @@ func (c *Client) addCallback(id int, ch chan rpcResponse) {
195
210
panicf ("OVSDB callback with ID %d already registered" , id )
196
211
}
197
212
198
- c .callbacks [id ] = ch
213
+ c .callbacks [id ] = cb
199
214
}
200
215
201
216
// doCallback performs a callback for an RPC response and clears the
@@ -204,27 +219,24 @@ func (c *Client) doCallback(id int, res rpcResponse) {
204
219
c .cbMu .Lock ()
205
220
defer c .cbMu .Unlock ()
206
221
207
- ch , ok := c .callbacks [id ]
222
+ cb , ok := c .callbacks [id ]
208
223
if ! ok {
209
224
// Nobody is listening to this callback.
210
225
return
211
226
}
212
227
213
- // Return result and remove this callback.
214
- ch <- res
215
- delete (c .callbacks , id )
216
- }
228
+ // Producer can safely close channel on return.
229
+ defer close (cb .Response )
217
230
218
- // cancelCallback is invoked when an RPC is canceled by its context.
219
- func (c * Client ) cancelCallback (ctx context.Context , id int ) error {
220
- // RPC canceled; acquire the callback mutex and clean up the callback
221
- // for this RPC.
222
- c .cbMu .Lock ()
223
- defer c .cbMu .Unlock ()
231
+ // Wait for send or cancelation.
232
+ select {
233
+ case <- cb .Ctx .Done ():
234
+ // Request's context was canceled.
235
+ case cb .Response <- res :
236
+ // Message was successfully sent.
237
+ }
224
238
225
239
delete (c .callbacks , id )
226
-
227
- return ctx .Err ()
228
240
}
229
241
230
242
func panicf (format string , a ... interface {}) {
0 commit comments