A prior commit, aa1ff990, switched away from using get_event_loop *by
default*, but this is not good enough to avoid deprecation warnings as
`asyncio.get_event_loop_policy().get_event_loop()` is *also*
deprecated. Replace this mechanism with explicit calls to
asyncio.get_new_loop() and revise the cleanup mechanisms in __del__ to
match.
Reported-by: Richard W.M. Jones <rjones@redhat.com>
Reported-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: John Snow <jsnow@redhat.com>
cherry picked from commit 21ce2ee4f2df87efe84a27b9c5112487f4670622
Signed-off-by: John Snow <jsnow@redhat.com>
---
python/qemu/qmp/legacy.py | 47 +++++++++++++++++++++++++++-----------
python/qemu/qmp/qmp_tui.py | 10 ++++++--
2 files changed, 42 insertions(+), 15 deletions(-)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index 735d42971e9..775b1fdd3b3 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -86,13 +86,15 @@ def __init__(self,
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
+ self._created_loop = False
try:
self._aloop = asyncio.get_running_loop()
except RuntimeError:
- # No running loop; since this is a sync shim likely to be
- # used in fully sync programs, create one if neccessary.
- self._aloop = asyncio.get_event_loop_policy().get_event_loop()
+ # No running loop; since this is a sync shim likely to be used
+ # in sync programs without any event loop at all, create one.
+ self._aloop = asyncio.new_event_loop()
+ self._created_loop = True
self._address = address
self._timeout: Optional[float] = None
@@ -313,17 +315,36 @@ def send_fd_scm(self, fd: int) -> None:
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 we created our own loop (and we are not running inside
+ # of it), we must close it to avoid warnings and error
+ # messages upon program exit.
+ if self._created_loop:
+ self._aloop.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"
)
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 12bdc17c99e..d5526338f22 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -161,6 +161,7 @@ def __init__(self, address: Union[str, Tuple[str, int]], num_retries: int,
self.retry_delay = retry_delay if retry_delay else 2
self.retry: bool = False
self.exiting: bool = False
+ self._created_loop = False
super().__init__()
def add_to_history(self, msg: str, level: Optional[str] = None) -> None:
@@ -391,8 +392,9 @@ def run(self, debug: bool = False) -> None:
try:
self.aloop = asyncio.get_running_loop()
except RuntimeError:
- # No running asyncio event loop. Create one if necessary.
- self.aloop = asyncio.get_event_loop_policy().get_event_loop()
+ # No running asyncio event loop. Create one.
+ self.aloop = asyncio.new_event_loop()
+ self._created_loop = True
self.aloop.set_debug(debug)
@@ -416,6 +418,10 @@ def run(self, debug: bool = False) -> None:
logging.error('%s\n%s\n', str(err), pretty_traceback())
raise err
+ def __del__(self) -> None:
+ if self._created_loop and self.aloop:
+ self.aloop.close()
+
class StatusBar(urwid.Text):
"""
--
2.50.1