1
- // Copyright 2016 The go-qemu Authors.
1
+ // Copyright 2022 The go-qemu Authors.
2
2
//
3
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
4
// you may not use this file except in compliance with the License.
@@ -27,6 +27,7 @@ import (
27
27
"os"
28
28
"path/filepath"
29
29
"strings"
30
+ "sync"
30
31
"time"
31
32
32
33
"github.com/digitalocean/go-qemu/qmp"
@@ -40,13 +41,14 @@ var (
40
41
41
42
// Domain represents a QEMU instance.
42
43
type Domain struct {
43
- Name string
44
- m qmp.Monitor
45
- rm * raw.Monitor
46
- done chan struct {}
47
- connect chan chan qmp.Event
48
- disconnect chan chan qmp.Event
49
- listeners []chan qmp.Event
44
+ Name string
45
+ m qmp.Monitor
46
+ rm * raw.Monitor
47
+ cancel context.CancelFunc
48
+ listeners struct {
49
+ sync.Mutex
50
+ value []chan <- qmp.Event
51
+ }
50
52
51
53
eventsUnsupported bool
52
54
@@ -57,7 +59,7 @@ type Domain struct {
57
59
// qmp.Monitor. Close must be called when done with a Domain to avoid leaking
58
60
// resources.
59
61
func (d * Domain ) Close () error {
60
- close ( d . done )
62
+ d . cancel ( )
61
63
return d .m .Disconnect ()
62
64
}
63
65
@@ -358,48 +360,45 @@ func (d *Domain) PackageVersion() (string, error) {
358
360
return vers .Package , nil
359
361
}
360
362
361
- // Events streams QEMU QMP events.
362
- // Two channels are returned, the first contains events emitted by the domain.
363
- // The second is used to signal completion of event processing.
364
- // It is the responsibility of the caller to always signal when finished.
363
+ // Events streams QEMU QMP events. Two channels are returned, the first contains
364
+ // events emitted by the domain. The second is used to signal completion of
365
+ // event processing. It is the responsibility of the caller to always close this
366
+ // channel when finished.
365
367
func (d * Domain ) Events () (chan qmp.Event , chan struct {}, error ) {
366
368
if d .eventsUnsupported {
367
369
return nil , nil , qmp .ErrEventsNotSupported
368
370
}
369
371
370
372
stream := make (chan qmp.Event )
371
- done := make (chan struct {})
373
+ // The previous expectation was that you write to this channel, not
374
+ // close it, so ensure we continue to support this.
375
+ done := make (chan struct {}, 1 )
372
376
373
377
// handle disconnection
374
378
go func () {
375
379
<- done
376
- // drain anything that gets sent on the channel
377
- // because the disconnect won't be processed if the
378
- // listenAndServe loop is waiting for the listener
379
- // to read from the unbuffered channel.
380
+ // If the caller has indicated they are done, they will
381
+ // no longer be reading from the stream, and there needs
382
+ // to be something which unblocks the main broadcast
383
+ // goroutine for writes to this stream so we can remove
384
+ // the stream from the list of listeners.
380
385
go func () {
381
386
for range stream {
382
387
}
383
388
}()
384
- d .disconnect <- stream
385
- close (stream )
386
- close (done )
389
+ d .closeAndRemoveListener (stream )
387
390
}()
388
391
389
- // add stream to broadcast
390
- d .connect <- stream
391
-
392
+ d .addListener (stream )
392
393
return stream , done , nil
393
394
}
394
395
395
396
// listenAndServe handles a domain's event broadcast service.
396
- func (d * Domain ) listenAndServe () error {
397
- ctx , cancel := context .WithCancel (context .Background ())
397
+ func (d * Domain ) listenAndServe (ctx context.Context ) error {
398
398
stream , err := d .m .Events (ctx )
399
399
if err != nil {
400
- cancel ()
401
400
// let Event() inform the user events are not supported
402
- if err == qmp .ErrEventsNotSupported {
401
+ if errors . Is ( err , qmp .ErrEventsNotSupported ) {
403
402
d .eventsUnsupported = true
404
403
return nil
405
404
}
@@ -408,41 +407,56 @@ func (d *Domain) listenAndServe() error {
408
407
}
409
408
410
409
go func () {
411
- defer cancel ()
412
- for {
413
- select {
414
- case <- d .done :
415
- return
416
- case client := <- d .connect :
417
- d .addListener (client )
418
- case client := <- d .disconnect :
419
- d .removeListener (client )
420
- case event := <- stream :
421
- d .broadcast (event )
422
- }
410
+ // When we're done broadcasting, ensure all of our listeners
411
+ // become aware.
412
+ defer d .closeAndRemoveListeners ()
413
+ for event := range stream {
414
+ d .broadcast (event )
423
415
}
424
416
}()
425
417
426
418
return nil
427
419
}
428
420
429
- // addListener adds the given stream to the domain's event broadcast.
430
- func (d * Domain ) addListener (stream chan qmp.Event ) {
431
- d .listeners = append (d .listeners , stream )
421
+ // addListener adds the given stream to the domain's event broadcast. The main
422
+ // broadcast goroutine takes ownership of the goroutine's lifetime.
423
+ func (d * Domain ) addListener (stream chan <- qmp.Event ) {
424
+ d .listeners .Lock ()
425
+ defer d .listeners .Unlock ()
426
+ d .listeners .value = append (d .listeners .value , stream )
427
+ }
428
+
429
+ // closeAndRemoveListeners closes all listeners and removes them from the list.
430
+ func (d * Domain ) closeAndRemoveListeners () {
431
+ d .listeners .Lock ()
432
+ defer d .listeners .Unlock ()
433
+ for _ , l := range d .listeners .value {
434
+ close (l )
435
+ }
436
+ d .listeners .value = nil
432
437
}
433
438
434
- // removeListener removes the given stream from the domain's event broadcast.
435
- func (d * Domain ) removeListener (stream chan qmp.Event ) {
436
- for i , client := range d .listeners {
439
+ // closeAndRemoveListener closes the listener and removes it from the domain's
440
+ // event broadcast.
441
+ func (d * Domain ) closeAndRemoveListener (stream chan <- qmp.Event ) {
442
+ d .listeners .Lock ()
443
+ defer d .listeners .Unlock ()
444
+
445
+ listeners := d .listeners .value
446
+ for i , client := range listeners {
437
447
if client == stream {
438
- d .listeners = append (d .listeners [:i ], d .listeners [i + 1 :]... )
448
+ close (client )
449
+ listeners = append (listeners [:i ], listeners [i + 1 :]... )
439
450
}
440
451
}
452
+ d .listeners .value = listeners
441
453
}
442
454
443
455
// broadcast sends the provided event to all event listeners.
444
456
func (d * Domain ) broadcast (event qmp.Event ) {
445
- for _ , stream := range d .listeners {
457
+ d .listeners .Lock ()
458
+ defer d .listeners .Unlock ()
459
+ for _ , stream := range d .listeners .value {
446
460
stream <- event
447
461
}
448
462
}
@@ -451,13 +465,15 @@ func (d *Domain) broadcast(event qmp.Event) {
451
465
// QMP communication is handled by the provided monitor socket.
452
466
func NewDomain (m qmp.Monitor , name string ) (* Domain , error ) {
453
467
d := & Domain {
454
- Name : name ,
455
- m : m ,
456
- rm : raw .NewMonitor (m ),
457
- done : make (chan struct {}),
458
- connect : make (chan chan qmp.Event ),
459
- disconnect : make (chan chan qmp.Event ),
460
- listeners : []chan qmp.Event {},
468
+ Name : name ,
469
+ m : m ,
470
+ rm : raw .NewMonitor (m ),
471
+ listeners : struct {
472
+ sync.Mutex
473
+ value []chan <- qmp.Event
474
+ }{
475
+ value : []chan <- qmp.Event {},
476
+ },
461
477
462
478
// By default, try to generate decently random file names
463
479
// for temporary files.
@@ -474,7 +490,13 @@ func NewDomain(m qmp.Monitor, name string) (*Domain, error) {
474
490
}
475
491
476
492
// start event broadcast
477
- err := d .listenAndServe ()
493
+ ctx , cancel := context .WithCancel (context .Background ())
494
+ d .cancel = cancel
495
+ err := d .listenAndServe (ctx )
496
+ if err != nil {
497
+ cancel ()
498
+ return nil , err
499
+ }
478
500
479
- return d , err
501
+ return d , nil
480
502
}
0 commit comments