From 0d829abb3b1a437d65afec93b8473eefce5183d0 Mon Sep 17 00:00:00 2001 From: Mark Ryan Date: Thu, 16 Feb 2017 09:45:14 +0000 Subject: [PATCH] qemu: Document how cancelling works. The code that handles the serialization and cancelling of QMP commands is a little complex and it took me some time to remember how it actually works and why it works in this particular way. For this reason I've added some comments which will hopefully make the next bug fix in this area a little less painful. Signed-off-by: Mark Ryan --- qemu/qmp.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/qemu/qmp.go b/qemu/qmp.go index c7ed0ec48..fc09543f8 100644 --- a/qemu/qmp.go +++ b/qemu/qmp.go @@ -346,6 +346,39 @@ func (q *QMP) parseVersion(version []byte) *QMPVersion { } } +// The qemu package allows multiple QMP commands to be submitted concurrently +// from different Go routines. Unfortunately, QMP doesn't really support parallel +// commands as there is no way reliable way to associate a command response +// with a request. For this reason we need to submit our commands to +// QMP serially. The qemu package performs this serialisation using a +// queue (cmdQueue owned by mainLoop). We use a queue rather than a simple +// mutex so we can support cancelling of commands (see below) and ordered +// execution of commands, i.e., if command B is issued before command C, +// it should be executed before command C even if both commands are initially +// blocked waiting for command A to finish. This would be hard to achieve with +// a simple mutex. +// +// Cancelling is a little tricky. Commands such as ExecuteQMPCapabilities +// can be cancelled by cancelling or timing out their contexts. When a +// command is cancelled the calling function, e.g., ExecuteQMPCapabilities, +// will return but we may not be able to remove the command's entry from +// the command queue or issue the next command. There are two scenarios +// here. +// +// 1. The command has been processed by QMP, i.e., we have received a +// return or an error, but is still blocking as it is waiting for +// an event. For example, the ExecuteDeviceDel blocks until a DEVICE_DELETED +// event is received. When such a command is cancelled we can remove it +// from the queue and start issuing the next command. When the DEVICE_DELETED +// event eventually arrives it will just be ignored. +// +// 2. The command has not been processed by QMP. In this case the command +// needs to remain on the cmdQueue until the response to this command is +// received from QMP. During this time no new commands can be issued. When the +// response is received, it is discarded (as no one is interested in the result +// any more), the entry is removed from the cmdQueue and we can proceed to +// execute the next command. + func (q *QMP) mainLoop() { cmdQueue := list.New().Init() fromVMCh := make(chan []byte)