diff options
Diffstat (limited to 'python/qemu/qmp/legacy.py')
-rw-r--r-- | python/qemu/qmp/legacy.py | 46 |
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" ) |