@@ -14,17 +14,19 @@ import (
1414 "sync/atomic"
1515
1616 "golang.org/x/sys/unix"
17+
18+ "github.com/lxc/incus/v6/shared/logger"
1719)
1820
1921type qemuMachineProtocal struct {
20- oobSupported bool // Out of band support or not
21- c net.Conn // Underlying connection
22- uc * net.UnixConn // Underlying unix socket connection
23- mu sync.Mutex // Serialize running command
24- stream <- chan rawResponse // Send command responses and errors
25- events <- chan qmpEvent // Events channel
26- listeners atomic.Uint32 // Listeners number
27- cid atomic.Uint32 // Auto increase command id
22+ oobSupported bool // Out of band support or not
23+ c net.Conn // Underlying connection
24+ uc * net.UnixConn // Underlying unix socket connection
25+ mu sync.Mutex // Serialize running command
26+ replies sync. Map // Replies channels
27+ events <- chan qmpEvent // Events channel
28+ listeners atomic.Uint32 // Listeners number
29+ cid atomic.Uint32 // Auto increase command id
2830}
2931
3032// qmpEvent represents a QEMU QMP event.
@@ -104,9 +106,8 @@ func (qmp *qemuMachineProtocal) disconnect() error {
104106
105107// qmpIncreaseID increase ID and skip zero.
106108func (qmp * qemuMachineProtocal ) qmpIncreaseID () uint32 {
107- const ZeroKey = uint32 (0 )
108109 id := qmp .cid .Add (1 )
109- if id == ZeroKey {
110+ if id == 0 {
110111 id = qmp .cid .Add (1 )
111112 }
112113
@@ -155,14 +156,10 @@ func (qmp *qemuMachineProtocal) connect() error {
155156 return fmt .Errorf ("reply id %d and command id %d mismatch" , r .ID , id )
156157 }
157158
158- // Initialize listener for command responses and asynchronous events
159- events := make (chan qmpEvent )
160- stream := make (chan rawResponse )
161- go qmp .listen (qmp .c , events , stream )
162-
159+ // Initialize listener for command responses and asynchronous events.
160+ events := make (chan qmpEvent , 128 )
161+ go qmp .listen (qmp .c , events , & qmp .replies )
163162 qmp .events = events
164- qmp .stream = stream
165-
166163 return nil
167164}
168165
@@ -172,9 +169,8 @@ func (qmp *qemuMachineProtocal) getEvents(context.Context) (<-chan qmpEvent, err
172169 return qmp .events , nil
173170}
174171
175- func (qmp * qemuMachineProtocal ) listen (r io.Reader , events chan <- qmpEvent , stream chan <- rawResponse ) {
172+ func (qmp * qemuMachineProtocal ) listen (r io.Reader , events chan <- qmpEvent , replies * sync. Map ) {
176173 defer close (events )
177- defer close (stream )
178174
179175 scanner := bufio .NewScanner (r )
180176 for scanner .Scan () {
@@ -194,9 +190,29 @@ func (qmp *qemuMachineProtocal) listen(r io.Reader, events chan<- qmpEvent, stre
194190 continue
195191 }
196192
193+ key := r .ID
194+ if key == 0 {
195+ // Discard response without a request ID.
196+ continue
197+ }
198+
199+ val , ok := replies .LoadAndDelete (key )
200+ if ! ok {
201+ // Discard unexpected response.
202+ continue
203+ }
204+
205+ reply , ok := val .(chan rawResponse )
206+ if ! ok {
207+ // Skip bad messages.
208+ logger .Error ("Failed to cast QMP reply to chan rawResponse" )
209+ continue
210+ }
211+
197212 r .raw = make ([]byte , len (b ))
198213 copy (r .raw , b )
199- stream <- r
214+ reply <- r
215+
200216 continue
201217 }
202218
@@ -205,19 +221,28 @@ func (qmp *qemuMachineProtocal) listen(r io.Reader, events chan<- qmpEvent, stre
205221 continue
206222 }
207223
208- events <- e
224+ select {
225+ case events <- e :
226+ logger .Debugf ("Event dispatched: %s" , b )
227+ default :
228+ logger .Debugf ("Event discarded: %s" , b )
229+ }
209230 }
210231
211232 err := scanner .Err ()
212233 if err != nil {
213- stream <- rawResponse {err : err }
234+ errReply := make (chan rawResponse , 1 )
235+ replies .Store (0 , errReply )
236+
237+ r := rawResponse {err : err }
238+ errReply <- r
214239 }
215240}
216241
217242// run executes the given QAPI command against a domain's QEMU instance.
218- func (qmp * qemuMachineProtocal ) run (command []byte ) ([]byte , error ) {
243+ func (qmp * qemuMachineProtocal ) run (command []byte , id uint32 ) ([]byte , error ) {
219244 // Just call RunWithFile with no file
220- return qmp .runWithFile (command , nil )
245+ return qmp .runWithFile (command , nil , id )
221246}
222247
223248func (qmp * qemuMachineProtocal ) qmpWriteMsg (b []byte , file * os.File ) error {
@@ -242,19 +267,33 @@ func (qmp *qemuMachineProtocal) qmpWriteMsg(b []byte, file *os.File) error {
242267}
243268
244269// runWithFile executes for passing a file through out-of-band data.
245- func (qmp * qemuMachineProtocal ) runWithFile (command []byte , file * os.File ) ([]byte , error ) {
270+ func (qmp * qemuMachineProtocal ) runWithFile (command []byte , file * os.File , id uint32 ) ([]byte , error ) {
246271 // Only allow a single command to be run at a time to ensure that responses
247272 // to a command cannot be mixed with responses from another command
248273 qmp .mu .Lock ()
249274 defer qmp .mu .Unlock ()
250275
276+ if id == 0 {
277+ id = qmp .qmpIncreaseID ()
278+ b , err := qmp .qmpInjectID (command , id )
279+ if err != nil {
280+ return nil , err
281+ }
282+
283+ command = b
284+ }
285+
286+ repCh := make (chan rawResponse , 1 )
287+ qmp .replies .Store (id , repCh )
288+
251289 err := qmp .qmpWriteMsg (command , file )
252290 if err != nil {
291+ qmp .replies .Delete (id )
253292 return nil , err
254293 }
255294
256295 // Wait for a response or error to our command
257- res := <- qmp . stream
296+ res := <- repCh
258297 if res .err != nil {
259298 return nil , res .err
260299 }
@@ -265,3 +304,19 @@ func (qmp *qemuMachineProtocal) runWithFile(command []byte, file *os.File) ([]by
265304
266305 return res .raw , nil
267306}
307+
308+ func (qmp * qemuMachineProtocal ) qmpInjectID (command []byte , id uint32 ) ([]byte , error ) {
309+ req := & qmpCommand {}
310+ err := json .Unmarshal (command , req )
311+ if err != nil {
312+ return nil , err
313+ }
314+
315+ req .ID = id
316+ b , err := json .Marshal (req )
317+ if err != nil {
318+ return nil , err
319+ }
320+
321+ return b , nil
322+ }
0 commit comments