Skip to content

Commit 0122ab2

Browse files
authored
[3.11] gh-94026: Buffer regrtest worker stdout in temporary file (GH-94253) (GH-94408)
Co-authored-by: Victor Stinner <[email protected]>. Co-authored-by: Christian Heimes <[email protected]>
1 parent aaa85b5 commit 0122ab2

File tree

1 file changed

+28
-26
lines changed

1 file changed

+28
-26
lines changed

Lib/test/libregrtest/runtest_mp.py

+28-26
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import signal
66
import subprocess
77
import sys
8+
import tempfile
89
import threading
910
import time
1011
import traceback
11-
from typing import NamedTuple, NoReturn, Literal, Any
12+
from typing import NamedTuple, NoReturn, Literal, Any, TextIO
1213

1314
from test import support
1415
from test.support import os_helper
@@ -51,7 +52,7 @@ def parse_worker_args(worker_args) -> tuple[Namespace, str]:
5152
return (ns, test_name)
5253

5354

54-
def run_test_in_subprocess(testname: str, ns: Namespace) -> subprocess.Popen:
55+
def run_test_in_subprocess(testname: str, ns: Namespace, stdout_fh: TextIO) -> subprocess.Popen:
5556
ns_dict = vars(ns)
5657
worker_args = (ns_dict, testname)
5758
worker_args = json.dumps(worker_args)
@@ -67,18 +68,17 @@ def run_test_in_subprocess(testname: str, ns: Namespace) -> subprocess.Popen:
6768
# Running the child from the same working directory as regrtest's original
6869
# invocation ensures that TEMPDIR for the child is the same when
6970
# sysconfig.is_python_build() is true. See issue 15300.
70-
kw = {}
71+
kw = dict(
72+
stdout=stdout_fh,
73+
# bpo-45410: Write stderr into stdout to keep messages order
74+
stderr=stdout_fh,
75+
text=True,
76+
close_fds=(os.name != 'nt'),
77+
cwd=os_helper.SAVEDCWD,
78+
)
7179
if USE_PROCESS_GROUP:
7280
kw['start_new_session'] = True
73-
return subprocess.Popen(cmd,
74-
stdout=subprocess.PIPE,
75-
# bpo-45410: Write stderr into stdout to keep
76-
# messages order
77-
stderr=subprocess.STDOUT,
78-
universal_newlines=True,
79-
close_fds=(os.name != 'nt'),
80-
cwd=os_helper.SAVEDCWD,
81-
**kw)
81+
return subprocess.Popen(cmd, **kw)
8282

8383

8484
def run_tests_worker(ns: Namespace, test_name: str) -> NoReturn:
@@ -204,12 +204,12 @@ def mp_result_error(
204204
test_result.duration_sec = time.monotonic() - self.start_time
205205
return MultiprocessResult(test_result, stdout, err_msg)
206206

207-
def _run_process(self, test_name: str) -> tuple[int, str, str]:
207+
def _run_process(self, test_name: str, stdout_fh: TextIO) -> int:
208208
self.start_time = time.monotonic()
209209

210210
self.current_test_name = test_name
211211
try:
212-
popen = run_test_in_subprocess(test_name, self.ns)
212+
popen = run_test_in_subprocess(test_name, self.ns, stdout_fh)
213213

214214
self._killed = False
215215
self._popen = popen
@@ -226,10 +226,10 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
226226
raise ExitThread
227227

228228
try:
229-
# bpo-45410: stderr is written into stdout
230-
stdout, _ = popen.communicate(timeout=self.timeout)
231-
retcode = popen.returncode
229+
# gh-94026: stdout+stderr are written to tempfile
230+
retcode = popen.wait(timeout=self.timeout)
232231
assert retcode is not None
232+
return retcode
233233
except subprocess.TimeoutExpired:
234234
if self._stopped:
235235
# kill() has been called: communicate() fails on reading
@@ -244,17 +244,12 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
244244
# bpo-38207: Don't attempt to call communicate() again: on it
245245
# can hang until all child processes using stdout
246246
# pipes completes.
247-
stdout = ''
248247
except OSError:
249248
if self._stopped:
250249
# kill() has been called: communicate() fails
251250
# on reading closed stdout
252251
raise ExitThread
253252
raise
254-
else:
255-
stdout = stdout.strip()
256-
257-
return (retcode, stdout)
258253
except:
259254
self._kill()
260255
raise
@@ -264,7 +259,17 @@ def _run_process(self, test_name: str) -> tuple[int, str, str]:
264259
self.current_test_name = None
265260

266261
def _runtest(self, test_name: str) -> MultiprocessResult:
267-
retcode, stdout = self._run_process(test_name)
262+
# gh-94026: Write stdout+stderr to a tempfile as workaround for
263+
# non-blocking pipes on Emscripten with NodeJS.
264+
with tempfile.TemporaryFile(
265+
'w+', encoding=sys.stdout.encoding
266+
) as stdout_fh:
267+
# gh-93353: Check for leaked temporary files in the parent process,
268+
# since the deletion of temporary files can happen late during
269+
# Python finalization: too late for libregrtest.
270+
retcode = self._run_process(test_name, stdout_fh)
271+
stdout_fh.seek(0)
272+
stdout = stdout_fh.read().strip()
268273

269274
if retcode is None:
270275
return self.mp_result_error(Timeout(test_name), stdout)
@@ -311,9 +316,6 @@ def run(self) -> None:
311316
def _wait_completed(self) -> None:
312317
popen = self._popen
313318

314-
# stdout must be closed to ensure that communicate() does not hang
315-
popen.stdout.close()
316-
317319
try:
318320
popen.wait(JOIN_TIMEOUT)
319321
except (subprocess.TimeoutExpired, OSError) as exc:

0 commit comments

Comments
 (0)