summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark A. Tsuchida <matsuchida@wisc.edu>2024-08-14 13:43:22 -0500
committerJussi Pakkanen <jpakkane@gmail.com>2024-08-25 22:36:13 +0300
commita57c880368379091a7fc6ee9916a5a80042e9bf2 (patch)
tree30e59b8e0d968f6c7207048a9289ddcec7b1d6f0
parent0bd45b3676b6100cb58a5a15bc9e783ec1d185c1 (diff)
downloadmeson-a57c880368379091a7fc6ee9916a5a80042e9bf2.tar.gz
Better handle CTRL-C during clang-tidy/format
It was possible (with some frequency) for the clang-tidy/format target to continue starting new subprocesses after a CTRL-C, because we were not canceling the already queued tasks and waiting for all of them. This makes a best-effort attempt to cancel all further subprocesses. It is not 100%, because there is a race that is hard to avoid (without major restructuring, at least): new subprocesses may be started after KeyboardInterrupt (or any other exception) is raised but before we get to the cancellation. When the race happens, the calling ninja may exit before Meson exits, causing some output (from clang-tidy/format and Meson's traceback) to be printed after returning to the shell prompt. But this is an improvement over potentially launching all the remaining tasks after having returned to the shell, which is what used to happen rather often. In practice, it appears that we cleanly exit with a pretty high probability unless CTRL-C is hit very early after starting (presumably before the thread pool has launched subprocesses on all its threads).
-rw-r--r--mesonbuild/scripts/run_tool.py22
1 files changed, 18 insertions, 4 deletions
diff --git a/mesonbuild/scripts/run_tool.py b/mesonbuild/scripts/run_tool.py
index a1641e90a..a84de15b1 100644
--- a/mesonbuild/scripts/run_tool.py
+++ b/mesonbuild/scripts/run_tool.py
@@ -5,8 +5,8 @@ from __future__ import annotations
import itertools
import fnmatch
+import concurrent.futures
from pathlib import Path
-from concurrent.futures import ThreadPoolExecutor
from ..compilers import lang_suffixes
from ..mesonlib import quiet_git
@@ -46,13 +46,27 @@ def run_tool(name: str, srcdir: Path, builddir: Path, fn: T.Callable[..., subpro
suffixes = {f'.{s}' for s in suffixes}
futures = []
returncode = 0
- with ThreadPoolExecutor() as e:
+ e = concurrent.futures.ThreadPoolExecutor()
+ try:
for f in itertools.chain(*globs):
strf = str(f)
if f.is_dir() or f.suffix not in suffixes or \
any(fnmatch.fnmatch(strf, i) for i in ignore):
continue
futures.append(e.submit(fn, f, *args))
- if futures:
- returncode = max(x.result().returncode for x in futures)
+ concurrent.futures.wait(
+ futures,
+ return_when=concurrent.futures.FIRST_EXCEPTION
+ )
+ finally:
+ # We try to prevent new subprocesses from being started by canceling
+ # the futures, but this is not water-tight: some may have started
+ # between the wait being interrupted or exited and the futures being
+ # canceled. (A fundamental fix would probably require the ability to
+ # terminate such subprocesses upon cancellation of the future.)
+ for x in futures: # Python >=3.9: e.shutdown(cancel_futures=True)
+ x.cancel()
+ e.shutdown()
+ if futures:
+ returncode = max(x.result().returncode for x in futures)
return returncode