@@ -2,12 +2,12 @@ package ts3
2
2
3
3
import (
4
4
"bufio"
5
- "errors"
6
5
"fmt"
7
6
"io"
8
7
"net"
9
8
"regexp"
10
9
"strings"
10
+ "sync"
11
11
"time"
12
12
13
13
"golang.org/x/crypto/ssh"
@@ -26,13 +26,18 @@ const (
26
26
// startBufSize is the initial size of allocation for the parse buffer.
27
27
startBufSize = 4096
28
28
29
- // keepAliveData is the keepalive data .
30
- keepAliveData = " \n "
29
+ // responseErrTimeout is the timeout used for sending response errors .
30
+ responseErrTimeout = time . Millisecond * 100
31
31
)
32
32
33
33
var (
34
+ // respTrailerRe is the regexp which matches a server response to a command.
34
35
respTrailerRe = regexp .MustCompile (`^error id=(\d+) msg=([^ ]+)(.*)` )
35
36
37
+ // keepAliveData is data which will be ignored by the server used to ensure
38
+ // the connection is kept alive.
39
+ keepAliveData = []byte (" \n " )
40
+
36
41
// DefaultTimeout is the default read / write / dial timeout for Clients.
37
42
DefaultTimeout = 10 * time .Second
38
43
@@ -52,6 +57,11 @@ type Connection interface {
52
57
Connect (addr string , timeout time.Duration ) error
53
58
}
54
59
60
+ type response struct {
61
+ err error
62
+ lines []string
63
+ }
64
+
55
65
// Client is a TeamSpeak 3 ServerQuery client.
56
66
type Client struct {
57
67
conn Connection
@@ -62,11 +72,13 @@ type Client struct {
62
72
maxBufSize int
63
73
notifyBufSize int
64
74
work chan string
65
- err chan error
75
+ response chan response
66
76
notify chan Notification
67
- disconnect chan struct {}
68
- res []string
77
+ closing chan struct {} // closing is closed to indicate we're closing our connection.
78
+ done chan struct {} // done is closed once we're seen a fatal error.
79
+ doneOnce sync.Once
69
80
connectHeader string
81
+ wg sync.WaitGroup
70
82
71
83
Server * ServerMethods
72
84
}
@@ -151,8 +163,9 @@ func NewClient(addr string, options ...func(c *Client) error) (*Client, error) {
151
163
maxBufSize : MaxParseTokenSize ,
152
164
notifyBufSize : DefaultNotifyBufSize ,
153
165
work : make (chan string ),
154
- err : make (chan error ),
155
- disconnect : make (chan struct {}),
166
+ response : make (chan response ),
167
+ closing : make (chan struct {}),
168
+ done : make (chan struct {}),
156
169
connectHeader : DefaultConnectHeader ,
157
170
}
158
171
for _ , f := range options {
@@ -183,7 +196,7 @@ func NewClient(addr string, options ...func(c *Client) error) (*Client, error) {
183
196
184
197
// Read the connection header
185
198
if ! c .scanner .Scan () {
186
- return nil , c .scanErr ()
199
+ return nil , fmt . Errorf ( "client: header: %w" , c .scanErr () )
187
200
}
188
201
189
202
if l := c .scanner .Text (); l != c .connectHeader {
@@ -192,30 +205,67 @@ func NewClient(addr string, options ...func(c *Client) error) (*Client, error) {
192
205
193
206
// Slurp the banner
194
207
if ! c .scanner .Scan () {
195
- return nil , c .scanErr ()
208
+ return nil , fmt . Errorf ( "client: banner: %w" , c .scanErr () )
196
209
}
197
210
198
211
if err := c .conn .SetReadDeadline (time.Time {}); err != nil {
199
212
return nil , fmt .Errorf ("client: set read deadline: %w" , err )
200
213
}
201
214
202
215
// Start handlers
216
+ c .wg .Add (2 )
203
217
go c .messageHandler ()
204
218
go c .workHandler ()
205
219
206
220
return c , nil
207
221
}
208
222
223
+ // fatalError returns false if err is nil otherwise it ensures
224
+ // that done is closed and returns true.
225
+ func (c * Client ) fatalError (err error ) bool {
226
+ if err == nil {
227
+ return false
228
+ }
229
+
230
+ c .closeDone ()
231
+ return true
232
+ }
233
+
234
+ // closeDone safely closes c.done.
235
+ func (c * Client ) closeDone () {
236
+ c .doneOnce .Do (func () {
237
+ close (c .done )
238
+ })
239
+ }
240
+
209
241
// messageHandler scans incoming lines and handles them accordingly.
242
+ // - Notifications are sent to c.notify.
243
+ // - ExecCmd responses are sent to c.response.
244
+ // If a fatal error occurs it stops processing and exits.
210
245
func (c * Client ) messageHandler () {
246
+ defer func () {
247
+ close (c .notify )
248
+ c .wg .Done ()
249
+ }()
250
+
251
+ buf := make ([]string , 0 , 10 )
211
252
for {
212
253
if c .scanner .Scan () {
213
254
line := c .scanner .Text ()
214
- //nolint: gocritic
215
255
if line == "error id=0 msg=ok" {
216
- c .err <- nil
256
+ var resp response
257
+ // Avoid creating a new buf if there was no data in the response.
258
+ if len (buf ) > 0 {
259
+ resp .lines = buf
260
+ buf = make ([]string , 0 , 10 )
261
+ }
262
+ c .response <- resp
217
263
} else if matches := respTrailerRe .FindStringSubmatch (line ); len (matches ) == 4 {
218
- c .err <- NewError (matches )
264
+ c .response <- response {err : NewError (matches )}
265
+ // Avoid creating a new buf if there was no data in the response.
266
+ if len (buf ) > 0 {
267
+ buf = make ([]string , 0 , 10 )
268
+ }
219
269
} else if strings .Index (line , "notify" ) == 0 {
220
270
if n , err := decodeNotification (line ); err == nil {
221
271
// non-blocking write
@@ -225,40 +275,70 @@ func (c *Client) messageHandler() {
225
275
}
226
276
}
227
277
} else {
228
- c .res = append (c .res , line )
278
+ // Partial response.
279
+ buf = append (buf , line )
229
280
}
230
281
} else {
231
- err := c .scanErr ()
232
- c . err <- err
233
- if errors . Is ( err , io . ErrUnexpectedEOF ) {
234
- close ( c . disconnect )
235
- return
282
+ if err := c .scanErr (); c . fatalError ( err ) {
283
+ c . responseErr ( err )
284
+ } else {
285
+ // Ensure that done is closed as scanner has seen an io.EOF.
286
+ c . closeDone ()
236
287
}
288
+ return
237
289
}
238
290
}
239
291
}
240
292
293
+ // responseErr sends err to c.response with a timeout to ensure it
294
+ // doesn't block forever when multiple errors occur during the
295
+ // processing of a single ExecCmd call.
296
+ func (c * Client ) responseErr (err error ) {
297
+ t := time .NewTimer (responseErrTimeout )
298
+ defer t .Stop ()
299
+
300
+ select {
301
+ case c .response <- response {err : err }:
302
+ case <- t .C :
303
+ }
304
+ }
305
+
241
306
// workHandler handles commands and keepAlive messages.
242
307
func (c * Client ) workHandler () {
308
+ defer c .wg .Done ()
309
+
243
310
for {
244
311
select {
245
312
case w := <- c .work :
246
- c .process (w )
313
+ if err := c .write ([]byte (w )); c .fatalError (err ) {
314
+ // Command send failed, inform the caller.
315
+ c .responseErr (err )
316
+ return
317
+ }
247
318
case <- time .After (c .keepAlive ):
248
- c .process (keepAliveData )
249
- case <- c .disconnect :
319
+ // Send a keep alive to prevent the connection from timing out.
320
+ if err := c .write (keepAliveData ); c .fatalError (err ) {
321
+ // We don't send to c.response as no ExecCmd is expecting a
322
+ // response and the next caller will get an error.
323
+ return
324
+ }
325
+ case <- c .done :
250
326
return
251
327
}
252
328
}
253
329
}
254
330
255
- func (c * Client ) process (data string ) {
331
+ // write writes data to the clients connection with the configured timeout
332
+ // returning any error.
333
+ func (c * Client ) write (data []byte ) error {
256
334
if err := c .conn .SetWriteDeadline (time .Now ().Add (c .timeout )); err != nil {
257
- c . err <- err
335
+ return fmt . Errorf ( "set deadline: %w" , err )
258
336
}
259
- if _ , err := c .conn .Write ([] byte ( data ) ); err != nil {
260
- c . err <- err
337
+ if _ , err := c .conn .Write (data ); err != nil {
338
+ return fmt . Errorf ( "write: %w" , err )
261
339
}
340
+
341
+ return nil
262
342
}
263
343
264
344
// Exec executes cmd on the server and returns the response.
@@ -268,37 +348,36 @@ func (c *Client) Exec(cmd string) ([]string, error) {
268
348
269
349
// ExecCmd executes cmd on the server and returns the response.
270
350
func (c * Client ) ExecCmd (cmd * Cmd ) ([]string , error ) {
271
- if ! c .IsConnected () {
351
+ select {
352
+ case c .work <- cmd .String ():
353
+ case <- c .done :
272
354
return nil , ErrNotConnected
273
355
}
274
356
275
- c .work <- cmd .String ()
276
-
357
+ var resp response
277
358
select {
278
- case err : = <- c .err :
279
- if err != nil {
280
- return nil , err
359
+ case resp = <- c .response :
360
+ if resp . err != nil {
361
+ return nil , resp . err
281
362
}
282
363
case <- time .After (c .timeout ):
283
364
return nil , ErrTimeout
284
365
}
285
366
286
- res := c .res
287
- c .res = nil
288
-
289
367
if cmd .response != nil {
290
- if err := DecodeResponse (res , cmd .response ); err != nil {
368
+ if err := DecodeResponse (resp . lines , cmd .response ); err != nil {
291
369
return nil , err
292
370
}
293
371
}
294
372
295
- return res , nil
373
+ return resp . lines , nil
296
374
}
297
375
298
- // IsConnected returns whether the client is connected.
376
+ // IsConnected returns true if the client is connected,
377
+ // false otherwise.
299
378
func (c * Client ) IsConnected () bool {
300
379
select {
301
- case <- c .disconnect :
380
+ case <- c .done :
302
381
return false
303
382
default :
304
383
return true
@@ -307,8 +386,10 @@ func (c *Client) IsConnected() bool {
307
386
308
387
// Close closes the connection to the server.
309
388
func (c * Client ) Close () error {
310
- defer close ( c . notify )
389
+ defer c . wg . Wait ( )
311
390
391
+ // Signal we're expecting EOF.
392
+ close (c .closing )
312
393
_ , err := c .Exec ("quit" )
313
394
err2 := c .conn .Close ()
314
395
@@ -321,11 +402,23 @@ func (c *Client) Close() error {
321
402
return nil
322
403
}
323
404
324
- // scanError returns the error from the scanner if non-nil,
325
- // `io.ErrUnexpectedEOF` otherwise.
405
+ // scanError returns nil if c is closing else if the scanner returns a
406
+ // non-nil error it is returned, otherwise returns `io.ErrUnexpectedEOF`.
407
+ // Callers must have seen c.scanner.Scan() return false.
326
408
func (c * Client ) scanErr () error {
327
- if err := c .scanner .Err (); err != nil {
328
- return fmt .Errorf ("client: scan: %w" , err )
409
+ select {
410
+ case <- c .closing :
411
+ // We know we're closing the connection so ignore any errors
412
+ // an return nil. This prevents spurious errors being returned
413
+ // to the caller.
414
+ return nil
415
+ default :
416
+ if err := c .scanner .Err (); err != nil {
417
+ return fmt .Errorf ("scan: %w" , err )
418
+ }
419
+
420
+ // As caller has seen c.scanner.Scan() return false
421
+ // this must have been triggered by an unexpected EOF.
422
+ return io .ErrUnexpectedEOF
329
423
}
330
- return io .ErrUnexpectedEOF
331
424
}
0 commit comments