aboutsummaryrefslogtreecommitdiff
path: root/python/qemu/qmp/legacy.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/qemu/qmp/legacy.py')
-rw-r--r--python/qemu/qmp/legacy.py46
1 files changed, 33 insertions, 13 deletions
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index 22a2b56..060ed0e 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -38,6 +38,7 @@ from typing import (
from .error import QMPError
from .protocol import Runstate, SocketAddrT
from .qmp_client import QMPClient
+from .util import get_or_create_event_loop
#: QMPMessage is an entire QMP message of any kind.
@@ -86,10 +87,13 @@ class QEMUMonitorProtocol:
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
- self._aloop = asyncio.get_event_loop()
self._address = address
self._timeout: Optional[float] = None
+ # This is a sync shim intended for use in fully synchronous
+ # programs. Create and set an event loop if necessary.
+ self._aloop = get_or_create_event_loop()
+
if server:
assert not isinstance(self._address, socket.socket)
self._sync(self._qmp.start_server(self._address))
@@ -231,6 +235,9 @@ class QEMUMonitorProtocol:
:return: The first available QMP event, or None.
"""
+ # Kick the event loop to allow events to accumulate
+ self._sync(asyncio.sleep(0))
+
if not wait:
# wait is False/0: "do not wait, do not except."
if self._qmp.events.empty():
@@ -286,8 +293,8 @@ class QEMUMonitorProtocol:
"""
Set the timeout for QMP RPC execution.
- This timeout affects the `cmd`, `cmd_obj`, and `command` methods.
- The `accept`, `pull_event` and `get_event` methods have their
+ This timeout affects the `cmd`, `cmd_obj`, and `cmd_raw` methods.
+ The `accept`, `pull_event` and `get_events` methods have their
own configurable timeouts.
:param timeout:
@@ -303,17 +310,30 @@ class QEMUMonitorProtocol:
self._qmp.send_fd_scm(fd)
def __del__(self) -> None:
- if self._qmp.runstate == Runstate.IDLE:
- return
+ if self._qmp.runstate != Runstate.IDLE:
+ self._qmp.logger.warning(
+ "QEMUMonitorProtocol object garbage collected without a prior "
+ "call to close()"
+ )
if not self._aloop.is_running():
- self.close()
- else:
- # Garbage collection ran while the event loop was running.
- # Nothing we can do about it now, but if we don't raise our
- # own error, the user will be treated to a lot of traceback
- # they might not understand.
+ if self._qmp.runstate != Runstate.IDLE:
+ # If the user neglected to close the QMP session and we
+ # are not currently running in an asyncio context, we
+ # have the opportunity to close the QMP session. If we
+ # do not do this, the error messages presented over
+ # dangling async resources may not make any sense to the
+ # user.
+ self.close()
+
+ if self._qmp.runstate != Runstate.IDLE:
+ # If QMP is still not quiesced, it means that the garbage
+ # collector ran from a context within the event loop and we
+ # are simply too late to take any corrective action. Raise
+ # our own error to give meaningful feedback to the user in
+ # order to prevent pages of asyncio stacktrace jargon.
raise QMPError(
- "QEMUMonitorProtocol.close()"
- " was not called before object was garbage collected"
+ "QEMUMonitorProtocol.close() was not called before object was "
+ "garbage collected, and could not be closed due to GC running "
+ "in the event loop"
)