1
0
mirror of https://git.yoctoproject.org/poky synced 2026-05-07 16:59:22 +00:00

qemu: backport patches to support python 3.14

We use QEMU QMP python module to drive qemu in testimage. QMP uses
asyncIO and the method to get the event loop changed.

Backport the patches handling the depreciation to fix the error:
  ERROR: core-image-minimal-1.0-r0 do_testimage: Error executing a python function in exec_func_python() autogenerated:

  The stack trace of python calls that resulted in this exception/failure was:
  File: 'exec_func_python() autogenerated', lineno: 2, function: <module>
   *** 0002:do_testimage(d)
  ...
  File: '.../openembedded-core/meta/lib/oeqa/utils/qemurunner.py', lineno: 332, function: launch
       0331:                from qmp.legacy import QEMUMonitorProtocol
   *** 0332:                self.qmp = QEMUMonitorProtocol(os.path.basename(qmp_port))
  File: '.../build-ubuntu2604/tmp-glibc/work/qemux86_64-oe-linux/core-image-minimal/1.0/recipe-sysroot-native/usr/lib/qemu-python/qmp/legacy.py', lineno: 89, function: __init__
   *** 0089:        self._aloop = asyncio.get_event_loop()
  File: '/usr/lib/python3.14/asyncio/events.py', lineno: 715, function: get_event_loop
       0711:
       0712:        Returns an instance of EventLoop or raises an exception.
       0713:        """
       0714:        if self._local._loop is None:
   *** 0715:            raise RuntimeError('There is no current event loop in thread %r.'
       0716:                               % threading.current_thread().name)
       0717:
       0718:        return self._local._loop
  Exception: RuntimeError: There is no current event loop in thread 'MainThread'.

Both patches are in Qemu 10.2 (OE Core master version)

(From OE-Core rev: 28bab00b35af8bbe3455c8266e4c792fa2367c5d)

Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
Signed-off-by: Paul Barker <paul@pbarker.dev>
This commit is contained in:
Yoann Congal
2026-04-22 09:01:38 +02:00
committed by Paul Barker
parent 1a547d3bae
commit 3d2536f642
3 changed files with 293 additions and 0 deletions
+2
View File
@@ -43,6 +43,8 @@ SRC_URI = "https://download.qemu.org/${BPN}-${PV}.tar.xz \
file://qemu-guest-agent.udev \
file://CVE-2024-8354.patch \
file://CVE-2025-12464.patch \
file://0001-python-backport-Remove-deprecated-get_event_loop-cal.patch \
file://0002-python-backport-avoid-creating-additional-event-loop.patch \
"
UPSTREAM_CHECK_REGEX = "qemu-(?P<pver>\d+(\.\d+)+)\.tar"
@@ -0,0 +1,92 @@
From 120d060528d02e24b68ac06b44de34fb206b4319 Mon Sep 17 00:00:00 2001
From: John Snow <jsnow@redhat.com>
Date: Tue, 13 Aug 2024 09:35:30 -0400
Subject: [PATCH] python: backport 'Remove deprecated get_event_loop calls'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This method was deprecated in 3.12 because it ordinarily should not be
used from coroutines; if there is not a currently running event loop,
this automatically creates a new event loop - which is usually not what
you want from code that would ever run in the bottom half.
In our case, we do want this behavior in two places:
(1) The synchronous shim, for convenience: this allows fully sync
programs to use QEMUMonitorProtocol() without needing to set up an event
loop beforehand. This is intentional to fully box in the async
complexities into the legacy sync shim.
(2) The qmp_tui shell; instead of relying on asyncio.run to create and
run an asyncio program, we need to be able to pass the current asyncio
loop to urwid setup functions. For convenience, again, we create one if
one is not present to simplify the creation of the TUI appliance.
The remaining user of get_event_loop() was in fact one of the erroneous
users that should not have been using this function: if there's no
running event loop inside of a coroutine, you're in big trouble :)
Signed-off-by: John Snow <jsnow@redhat.com>
cherry picked from commit python-qemu-qmp@aa1ff9907603a3033296027e1bd021133df86ef1
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/5d99044d09db0fa8c2b3294e301927118f9effc9]
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
---
python/qemu/qmp/legacy.py | 9 ++++++++-
python/qemu/qmp/qmp_tui.py | 7 ++++++-
python/tests/protocol.py | 2 +-
3 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index 22a2b5616ef..ea9b8032c3b 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -86,7 +86,14 @@ def __init__(self,
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
- self._aloop = asyncio.get_event_loop()
+
+ 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()
+
self._address = address
self._timeout: Optional[float] = None
diff --git a/python/qemu/qmp/qmp_tui.py b/python/qemu/qmp/qmp_tui.py
index 2d9ebbd20bc..d11b9fc547b 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -377,7 +377,12 @@ def run(self, debug: bool = False) -> None:
screen = urwid.raw_display.Screen()
screen.set_terminal_properties(256)
- self.aloop = asyncio.get_event_loop()
+ 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()
+
self.aloop.set_debug(debug)
# Gracefully handle SIGTERM and SIGINT signals
diff --git a/python/tests/protocol.py b/python/tests/protocol.py
index 56c4d441f9c..8dcef573b6c 100644
--- a/python/tests/protocol.py
+++ b/python/tests/protocol.py
@@ -228,7 +228,7 @@ def async_test(async_test_method):
Decorator; adds SetUp and TearDown to async tests.
"""
async def _wrapper(self, *args, **kwargs):
- loop = asyncio.get_event_loop()
+ loop = asyncio.get_running_loop()
loop.set_debug(True)
await self._asyncSetUp()
@@ -0,0 +1,199 @@
From f25eb62190a6fa170db24584fe6225cd0dcd64ad Mon Sep 17 00:00:00 2001
From: John Snow <jsnow@redhat.com>
Date: Wed, 3 Sep 2025 01:06:30 -0400
Subject: [PATCH] python: backport 'avoid creating additional event loops per
thread'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
This commit is two backports squashed into one to avoid regressions.
python: *really* remove get_event_loop
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.
python: avoid creating additional event loops per thread
"Too hasty by far!", commit 21ce2ee4 attempted to avoid deprecated
behavior altogether by calling new_event_loop() directly if there was no
loop currently running, but this has the unfortunate side effect of
potentially creating multiple event loops per thread if tests
instantiate multiple QMP connections in a single thread. This behavior
is apparently not well-defined and causes problems in some, but not all,
combinations of Python interpreter version and platform environment.
Partially revert to Daniel Berrange's original patch, which calls
get_event_loop and simply suppresses the deprecation warning in
Python<=3.13. This time, however, additionally register new loops
created with new_event_loop() so that future calls to get_event_loop()
will return the loop already created.
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 python-qemu-qmp@21ce2ee4f2df87efe84a27b9c5112487f4670622
cherry picked from commit python-qemu-qmp@c08fb82b38212956ccffc03fc6d015c3979f42fe
Signed-off-by: John Snow <jsnow@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Upstream-Status: Backport [https://gitlab.com/qemu-project/qemu/-/commit/85f223e5b031eb8ab63fbca314a4fb296a3a2632]
Signed-off-by: Yoann Congal <yoann.congal@smile.fr>
---
python/qemu/qmp/legacy.py | 46 +++++++++++++++++++++++---------------
python/qemu/qmp/qmp_tui.py | 10 ++-------
python/qemu/qmp/util.py | 27 ++++++++++++++++++++++
3 files changed, 57 insertions(+), 26 deletions(-)
diff --git a/python/qemu/qmp/legacy.py b/python/qemu/qmp/legacy.py
index ea9b8032c3b..c732212c048 100644
--- a/python/qemu/qmp/legacy.py
+++ b/python/qemu/qmp/legacy.py
@@ -38,6 +38,7 @@
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,17 +87,13 @@ def __init__(self,
"server argument should be False when passing a socket")
self._qmp = QMPClient(nickname)
-
- 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()
-
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))
@@ -310,17 +307,30 @@ 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 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 d11b9fc547b..76e540931c7 100644
--- a/python/qemu/qmp/qmp_tui.py
+++ b/python/qemu/qmp/qmp_tui.py
@@ -40,7 +40,7 @@
from .message import DeserializationError, Message, UnexpectedTypeError
from .protocol import ConnectError, Runstate
from .qmp_client import ExecInterruptedError, QMPClient
-from .util import create_task, pretty_traceback
+from .util import get_or_create_event_loop, create_task, pretty_traceback
# The name of the signal that is used to update the history list
@@ -376,13 +376,7 @@ def run(self, debug: bool = False) -> None:
"""
screen = urwid.raw_display.Screen()
screen.set_terminal_properties(256)
-
- 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()
-
+ self.aloop = get_or_create_event_loop()
self.aloop.set_debug(debug)
# Gracefully handle SIGTERM and SIGINT signals
diff --git a/python/qemu/qmp/util.py b/python/qemu/qmp/util.py
index ca6225e9cda..213f09c6528 100644
--- a/python/qemu/qmp/util.py
+++ b/python/qemu/qmp/util.py
@@ -20,6 +20,7 @@
TypeVar,
cast,
)
+import warnings
T = TypeVar('T')
@@ -30,6 +31,32 @@
# --------------------------
+def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
+ """
+ Return this thread's current event loop, or create a new one.
+
+ This function behaves similarly to asyncio.get_event_loop() in
+ Python<=3.13, where if there is no event loop currently associated
+ with the current context, it will create and register one. It should
+ generally not be used in any asyncio-native applications.
+ """
+ try:
+ with warnings.catch_warnings():
+ # Python <= 3.13 will trigger deprecation warnings if no
+ # event loop is set, but will create and set a new loop.
+ warnings.simplefilter("ignore")
+ loop = asyncio.get_event_loop()
+ except RuntimeError:
+ # Python 3.14+: No event loop set for this thread,
+ # create and set one.
+ loop = asyncio.new_event_loop()
+ # Set this loop as the current thread's loop, to be returned
+ # by calls to get_event_loop() in the future.
+ asyncio.set_event_loop(loop)
+
+ return loop
+
+
async def flush(writer: asyncio.StreamWriter) -> None:
"""
Utility function to ensure a StreamWriter is *fully* drained.