From 3cd0a71531641e1a62dca47dee788e0d18b70bfe Mon Sep 17 00:00:00 2001 From: stratakis Date: Wed, 24 Jun 2026 15:46:56 +0200 Subject: [PATCH] gh-151496: Use process groups in TraceBackend in test_dtrace (GH-152039) Run the generic DTrace/SystemTap commands in a new process group and terminate the whole group on timeout. This prevents a forked tracer child from keeping stdout/stderr pipes open after the direct tracer process is killed. (cherry picked from commit 1785f4b35f899704df0be54cba3776906186b2b1) Co-authored-by: stratakis --- Lib/test/test_dtrace.py | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/Lib/test/test_dtrace.py b/Lib/test/test_dtrace.py index 592f59d77f9221..3de87fc704d43e 100644 --- a/Lib/test/test_dtrace.py +++ b/Lib/test/test_dtrace.py @@ -75,10 +75,13 @@ class TraceBackend: COMMAND_ARGS = [] def run_case(self, name, optimize_python=None): - actual_output = normalize_trace_output(self.trace_python( - script_file=abspath(name + self.EXTENSION), - python_file=abspath(name + ".py"), - optimize_python=optimize_python)) + try: + actual_output = normalize_trace_output(self.trace_python( + script_file=abspath(name + self.EXTENSION), + python_file=abspath(name + ".py"), + optimize_python=optimize_python)) + except subprocess.TimeoutExpired: + raise AssertionError(f"{self.COMMAND[0]} timed out") with open(abspath(name + self.EXTENSION + ".expected")) as f: expected_output = f.read().rstrip() @@ -91,12 +94,17 @@ def generate_trace_command(self, script_file, subcommand=None): command += ["-c", subcommand] return command - def trace(self, script_file, subcommand=None): + def trace(self, script_file, subcommand=None, *, timeout=None): command = self.generate_trace_command(script_file, subcommand) - stdout, _ = subprocess.Popen(command, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - universal_newlines=True).communicate() + proc = create_process_group(command, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + universal_newlines=True) + try: + stdout, _ = proc.communicate(timeout=timeout) + except subprocess.TimeoutExpired: + kill_process_group(proc) + raise return stdout def trace_python(self, script_file, python_file, optimize_python=None): @@ -104,12 +112,17 @@ def trace_python(self, script_file, python_file, optimize_python=None): if optimize_python: python_flags.extend(["-O"] * optimize_python) subcommand = " ".join([sys.executable] + python_flags + [python_file]) - return self.trace(script_file, subcommand) + return self.trace(script_file, subcommand, timeout=60) def assert_usable(self): try: - output = self.trace(abspath("assert_usable" + self.EXTENSION)) + output = self.trace(abspath("assert_usable" + self.EXTENSION), + timeout=10) output = output.strip() + except subprocess.TimeoutExpired: + raise unittest.SkipTest( + f"{self.COMMAND[0]} timed out during usability check" + ) except (FileNotFoundError, NotADirectoryError, PermissionError) as fnfe: output = str(fnfe) if output != "probe: success":