@@ -18,8 +18,12 @@ import (
18
18
"bytes"
19
19
"encoding/json"
20
20
"fmt"
21
+ "io"
21
22
"log"
22
23
"net"
24
+ "strings"
25
+ "sync"
26
+ "sync/atomic"
23
27
24
28
"github.com/digitalocean/go-openvswitch/ovsdb/internal/jsonrpc"
25
29
)
@@ -28,6 +32,20 @@ import (
28
32
type Client struct {
29
33
c * jsonrpc.Conn
30
34
ll * log.Logger
35
+
36
+ // Incremented atomically when sending RPCs.
37
+ rpcID * int64
38
+
39
+ // Callbacks for RPC responses.
40
+ cbMu sync.RWMutex
41
+ callbacks map [int ]chan rpcResponse
42
+
43
+ wg * sync.WaitGroup
44
+ }
45
+
46
+ type rpcResponse struct {
47
+ Result json.RawMessage
48
+ Error error
31
49
}
32
50
33
51
// An OptionFunc is a function which can configure a Client.
@@ -60,14 +78,41 @@ func New(conn net.Conn, options ...OptionFunc) (*Client, error) {
60
78
}
61
79
}
62
80
81
+ // Set up RPC request IDs.
82
+ var rpcID int64
83
+ client .rpcID = & rpcID
84
+
85
+ // Set up the JSON-RPC connection.
63
86
client .c = jsonrpc .NewConn (conn , client .ll )
64
87
88
+ // Set up callbacks.
89
+ client .callbacks = make (map [int ]chan rpcResponse )
90
+
91
+ // Start up any background routines.
92
+ var wg sync.WaitGroup
93
+ wg .Add (1 )
94
+
95
+ // Handle all incoming RPC responses and notifications.
96
+ go func () {
97
+ defer wg .Done ()
98
+ client .listen ()
99
+ }()
100
+
101
+ client .wg = & wg
102
+
65
103
return client , nil
66
104
}
67
105
106
+ // requestID returns the next available request ID for an RPC.
107
+ func (c * Client ) requestID () int {
108
+ return int (atomic .AddInt64 (c .rpcID , 1 ))
109
+ }
110
+
68
111
// Close closes a Client's connection.
69
112
func (c * Client ) Close () error {
70
- return c .c .Close ()
113
+ err := c .c .Close ()
114
+ c .wg .Wait ()
115
+ return err
71
116
}
72
117
73
118
// ListDatabases returns the name of all databases known to the OVSDB server.
@@ -82,6 +127,11 @@ func (c *Client) ListDatabases() ([]string, error) {
82
127
83
128
// rpc performs a single RPC request, and checks the response for errors.
84
129
func (c * Client ) rpc (method string , out interface {}, args ... interface {}) error {
130
+ // Unmarshal results into empty struct if no out specified.
131
+ if out == nil {
132
+ out = & struct {}{}
133
+ }
134
+
85
135
// Captures any OVSDB errors.
86
136
r := result {
87
137
Reply : out ,
@@ -90,10 +140,24 @@ func (c *Client) rpc(method string, out interface{}, args ...interface{}) error
90
140
req := jsonrpc.Request {
91
141
Method : method ,
92
142
Params : args ,
93
- // Let the client handle the request ID.
143
+ ID : c .requestID (),
144
+ }
145
+
146
+ // Add callback for this RPC ID to return results via channel.
147
+ ch := make (chan rpcResponse , 0 )
148
+ c .addCallback (req .ID , ch )
149
+
150
+ if err := c .c .Send (req ); err != nil {
151
+ return err
152
+ }
153
+
154
+ // Wait for callback to fire.
155
+ res := <- ch
156
+ if err := res .Error ; err != nil {
157
+ return err
94
158
}
95
159
96
- if err := c . c . Execute ( req , & r ); err != nil {
160
+ if err := json . Unmarshal ( res . Result , & r ); err != nil {
97
161
return err
98
162
}
99
163
@@ -105,6 +169,70 @@ func (c *Client) rpc(method string, out interface{}, args ...interface{}) error
105
169
return nil
106
170
}
107
171
172
+ // listen starts an RPC receive loop that can return RPC results to
173
+ // clients via a callback.
174
+ func (c * Client ) listen () {
175
+ for {
176
+ res , err := c .c .Receive ()
177
+ if err != nil {
178
+ // EOF or closed connection means time to stop serving.
179
+ if err == io .EOF || strings .Contains (err .Error (), "use of closed network" ) {
180
+ return
181
+ }
182
+
183
+ // For any other connection errors, just keep trying.
184
+ continue
185
+ }
186
+
187
+ // TODO(mdlayher): deal with RPC notifications.
188
+
189
+ // Handle any JSON-RPC top-level errors.
190
+ if err := res .Err (); err != nil {
191
+ c .doCallback (* res .ID , rpcResponse {
192
+ Error : err ,
193
+ })
194
+ continue
195
+ }
196
+
197
+ // Return RPC results via callback.
198
+ c .doCallback (* res .ID , rpcResponse {
199
+ Result : res .Result ,
200
+ })
201
+ }
202
+ }
203
+
204
+ // addCallback registers a callback for an RPC response for the specified ID,
205
+ // and accepts a channel to return the results on.
206
+ func (c * Client ) addCallback (id int , ch chan rpcResponse ) {
207
+ c .cbMu .Lock ()
208
+ defer c .cbMu .Unlock ()
209
+
210
+ if _ , ok := c .callbacks [id ]; ok {
211
+ // This ID was already registered.
212
+ panicf ("OVSDB callback with ID %d already registered" , id )
213
+ }
214
+
215
+ c .callbacks [id ] = ch
216
+ }
217
+
218
+ // doCallback performs a callback for an RPC response and clears the
219
+ // callback on completion.
220
+ func (c * Client ) doCallback (id int , res rpcResponse ) {
221
+ c .cbMu .Lock ()
222
+ defer c .cbMu .Unlock ()
223
+
224
+ ch , ok := c .callbacks [id ]
225
+ if ! ok {
226
+ // Nobody is listening to this callback.
227
+ panicf ("OVSDB callback with ID %d has no listeners" , id )
228
+ }
229
+
230
+ // Return result, clean up channel, and remove this callback.
231
+ ch <- res
232
+ close (ch )
233
+ delete (c .callbacks , id )
234
+ }
235
+
108
236
// A result is used to unmarshal JSON-RPC results, and to check for any errors.
109
237
type result struct {
110
238
Reply interface {}
@@ -143,3 +271,7 @@ type Error struct {
143
271
func (e * Error ) Error () string {
144
272
return fmt .Sprintf ("%s: %s: %s" , e .Err , e .Details , e .Syntax )
145
273
}
274
+
275
+ func panicf (format string , a ... interface {}) {
276
+ panic (fmt .Sprintf (format , a ... ))
277
+ }
0 commit comments