diff mbox series

[3/3] runqemu: Do not run cleanup in signal handler

Message ID 20261107-mathieu-master-next-b16487-v1-3-2fe0e7b9b1fb@bootlin.com
State New
Headers show
Series runqemu: Fix a deadlock resulting in files left behind | expand

Commit Message

Mathieu Dubois-Briand Nov. 7, 2026, 1:54 p.m. UTC
If QEMU is still running while we are in the cleanup() function, this
means we are currently handling a SIGTERM on runqemu. So we are still
waiting on the process in start_qemu(). Trying to manipulate the process
here might result in a deadlock.

As an alternative, SIGTERM handler is now limited to transferring this
SIGTERM to QEMU. Clean-up will happen in the normal path of the code. An
additional SIGALRM timer makes sure to kill the QEMU process if it's
still alive 5 seconds later.

[YOCTO #16487]

Signed-off-by: Mathieu Dubois-Briand <mathieu.dubois-briand@bootlin.com>
---
 scripts/runqemu | 35 +++++++++++++++++++----------------
 1 file changed, 19 insertions(+), 16 deletions(-)
diff mbox series

Patch

diff --git a/scripts/runqemu b/scripts/runqemu
index c9f6481cd11e..967a86b78304 100755
--- a/scripts/runqemu
+++ b/scripts/runqemu
@@ -227,8 +227,6 @@  class BaseConfig(object):
         # pid of the actual qemu process
         self.qemu_environ = os.environ.copy()
         self.qemuprocess = None
-        # avoid cleanup twice
-        self.cleaned = False
         # Files to cleanup after run
         self.cleanup_files = []
         self.qmp = None
@@ -1658,6 +1656,7 @@  to your build configuration.
         process = subprocess.Popen(cmds, stderr=subprocess.PIPE, pass_fds=pass_fds, env=self.qemu_environ)
         self.qemuprocess = process
         retcode = process.wait()
+        signal.alarm(0)
         if retcode:
             if retcode == -signal.SIGTERM:
                 logger.info("Qemu terminated by SIGTERM")
@@ -1672,22 +1671,11 @@  to your build configuration.
                 raise RunQemuError('Failed to run %s' % str(cmd))
 
     def cleanup(self):
-        if self.cleaned:
-            return
-
         # avoid dealing with SIGTERM when cleanup function is running
         signal.signal(signal.SIGTERM, signal.SIG_IGN)
 
         logger.info("Cleaning up")
 
-        if self.qemuprocess:
-            try:
-                # give it some time to shut down, ignore return values and output
-                self.qemuprocess.send_signal(signal.SIGTERM)
-                self.qemuprocess.communicate(timeout=5)
-            except subprocess.TimeoutExpired:
-                self.qemuprocess.kill()
-
         with open('/proc/uptime', 'r') as f:
             uptime_seconds = f.readline().split()[0]
         logger.info('Host uptime: %s\n' % uptime_seconds)
@@ -1718,8 +1706,6 @@  to your build configuration.
         # Deliberately ignore the return code of 'tput smam'.
         subprocess.call(["tput", "smam"])
 
-        self.cleaned = True
-
     def run_bitbake_env(self, mach=None, target=''):
 
         if not mach:
@@ -1806,9 +1792,26 @@  def main():
 
         def sigterm_handler(signum, frame):
             logger.info("Received signal: %s" % (signum))
-            config.cleanup()
+            # Stop qemu: process.wait() in start_qemu() will reap it. Add an
+            # alarm to send a SIGKILL if QEMU is still alive 5s later.
+            if config.qemuprocess is not None and config.qemuprocess.returncode is None:
+                try:
+                    config.qemuprocess.terminate()
+                except OSError:
+                    pass
+                signal.alarm(5)
         signal.signal(signal.SIGTERM, sigterm_handler)
 
+        def sigalrm_handler(signum, frame):
+            # Force-kill qemu if it is still running after the grace period.
+            # No-op once qemu has been reaped (returncode set).
+            if config.qemuprocess is not None and config.qemuprocess.returncode is None:
+                try:
+                    config.qemuprocess.kill()
+                except OSError:
+                    pass
+        signal.signal(signal.SIGALRM, sigalrm_handler)
+
         config.check_args()
         config.read_qemuboot()
         config.check_and_set()