Commit 8f260b02 authored by David Gow's avatar David Gow Committed by Shuah Khan
Browse files

kunit: tool: Terminate kernel under test on SIGINT



kunit.py will attempt to catch SIGINT / ^C in order to ensure the TTY isn't
messed up, but never actually attempts to terminate the running kernel (be
it UML or QEMU). This can lead to a bit of frustration if the kernel has
crashed or hung.

Terminate the kernel process in the signal handler, if it's running. This
requires plumbing through the process handle in a few more places (and
having some checks to see if the kernel is still running in places where it
may have already been killed).

Reported-by: default avatarAndy Shevchenko <andriy.shevchenko@intel.com>
Closes: https://lore.kernel.org/all/aaFmiAmg9S18EANA@smile.fi.intel.com/


Signed-off-by: default avatarDavid Gow <david@davidgow.net>
Reviewed-by: default avatarAndy Shevchenko <andriy.shevchenko@intel.com>
Tested-by: default avatarAndy Shevchenko <andriy.shevchenko@intel.com>
Signed-off-by: default avatarShuah Khan <skhan@linuxfoundation.org>
parent e42c349f
Loading
Loading
Loading
Loading
+19 −9
Original line number Diff line number Diff line
@@ -16,7 +16,7 @@ import shutil
import signal
import sys
import threading
from typing import Iterator, List, Optional, Tuple
from typing import Iterator, List, Optional, Tuple, Any
from types import FrameType

import kunit_config
@@ -265,6 +265,7 @@ class LinuxSourceTree:
		if kconfig_add:
			kconfig = kunit_config.parse_from_string('\n'.join(kconfig_add))
			self._kconfig.merge_in_entries(kconfig)
		self._process : Optional[subprocess.Popen[Any]] = None

	def arch(self) -> str:
		return self._arch
@@ -364,36 +365,45 @@ class LinuxSourceTree:
			args.append('kunit.filter_action=' + filter_action)
		args.append('kunit.enable=1')

		process = self._ops.start(args, build_dir)
		assert process.stdout is not None  # tell mypy it's set
		self._process = self._ops.start(args, build_dir)
		assert self._process is not None # tell mypy it's set
		assert self._process.stdout is not None  # tell mypy it's set

		# Enforce the timeout in a background thread.
		def _wait_proc() -> None:
			try:
				process.wait(timeout=timeout)
				if self._process:
					self._process.wait(timeout=timeout)
			except Exception as e:
				print(e)
				process.terminate()
				process.wait()
				if self._process:
					self._process.terminate()
					self._process.wait()
		waiter = threading.Thread(target=_wait_proc)
		waiter.start()

		output = open(get_outfile_path(build_dir), 'w')
		try:
			# Tee the output to the file and to our caller in real time.
			for line in process.stdout:
			for line in self._process.stdout:
				output.write(line)
				yield line
		# This runs even if our caller doesn't consume every line.
		finally:
			# Flush any leftover output to the file
			output.write(process.stdout.read())
			if self._process:
				if self._process.stdout:
					output.write(self._process.stdout.read())
					self._process.stdout.close()
				self._process = None
			output.close()
			process.stdout.close()

			waiter.join()
			self._restore_terminal_if_tty()

	def signal_handler(self, unused_sig: int, unused_frame: Optional[FrameType]) -> None:
		logging.error('Build interruption occurred. Cleaning console.')
		if self._process:
				self._process.terminate()
				self._process.wait()
		self._restore_terminal_if_tty()