Skip to content

Commit dab10f4

Browse files
authored
[3.5] bpo-30320, bpo-25277: backport test_eintr enhancements from master to 3.5 (#1532)
* bpo-30320: test_eintr now uses pthread_sigmask() (#1523) Rewrite sigwaitinfo() and sigtimedwait() unit tests for EINTR using pthread_sigmask() to fix a race condition between the child and the parent process. Remove the pipe which was used as a weak workaround against the race condition. sigtimedwait() is now tested with a child process sending a signal instead of testing the timeout feature which is more unstable (especially regarding to clock resolution depending on the platform). (cherry picked from commit 211a392) * test_eintr: Fix ResourceWarning warnings (cherry picked from commit c50cccf) * test_eintr: remove unused import * bpo-25277: Add a watchdog to test_eintr Set a timeout of 10 minutes in test_eintr using faulthandler.
1 parent 639e295 commit dab10f4

File tree

1 file changed

+38
-31
lines changed

1 file changed

+38
-31
lines changed

Lib/test/eintrdata/eintr_tester.py

Lines changed: 38 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"""
1010

1111
import contextlib
12-
import io
12+
import faulthandler
1313
import os
1414
import select
1515
import signal
@@ -50,6 +50,10 @@ def setUpClass(cls):
5050
signal.setitimer(signal.ITIMER_REAL, cls.signal_delay,
5151
cls.signal_period)
5252

53+
# Issue #25277: Use faulthandler to try to debug a hang on FreeBSD
54+
if hasattr(faulthandler, 'dump_traceback_later'):
55+
faulthandler.dump_traceback_later(10 * 60, exit=True)
56+
5357
@classmethod
5458
def stop_alarm(cls):
5559
signal.setitimer(signal.ITIMER_REAL, 0, 0)
@@ -58,6 +62,8 @@ def stop_alarm(cls):
5862
def tearDownClass(cls):
5963
cls.stop_alarm()
6064
signal.signal(signal.SIGALRM, cls.orig_handler)
65+
if hasattr(faulthandler, 'cancel_dump_traceback_later'):
66+
faulthandler.cancel_dump_traceback_later()
6167

6268
def subprocess(self, *args, **kw):
6369
cmd_args = (sys.executable, '-c') + args
@@ -77,6 +83,9 @@ def _test_wait_multiple(self, wait_func):
7783
processes = [self.new_sleep_process() for _ in range(num)]
7884
for _ in range(num):
7985
wait_func()
86+
# Call the Popen method to avoid a ResourceWarning
87+
for proc in processes:
88+
proc.wait()
8089

8190
def test_wait(self):
8291
self._test_wait_multiple(os.wait)
@@ -88,6 +97,8 @@ def test_wait3(self):
8897
def _test_wait_single(self, wait_func):
8998
proc = self.new_sleep_process()
9099
wait_func(proc.pid)
100+
# Call the Popen method to avoid a ResourceWarning
101+
proc.wait()
91102

92103
def test_waitpid(self):
93104
self._test_wait_single(lambda pid: os.waitpid(pid, 0))
@@ -358,59 +369,55 @@ def test_sleep(self):
358369

359370

360371
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
372+
# bpo-30320: Need pthread_sigmask() to block the signal, otherwise the test
373+
# is vulnerable to a race condition between the child and the parent processes.
374+
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
375+
'need signal.pthread_sigmask()')
361376
class SignalEINTRTest(EINTRBaseTest):
362377
""" EINTR tests for the signal module. """
363378

364-
@unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
365-
'need signal.sigtimedwait()')
366-
def test_sigtimedwait(self):
367-
t0 = time.monotonic()
368-
signal.sigtimedwait([signal.SIGUSR1], self.sleep_time)
369-
dt = time.monotonic() - t0
370-
self.assertGreaterEqual(dt, self.sleep_time)
371-
372-
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
373-
'need signal.sigwaitinfo()')
374-
def test_sigwaitinfo(self):
375-
# Issue #25277, #25868: give a few milliseconds to the parent process
376-
# between os.write() and signal.sigwaitinfo() to works around a race
377-
# condition
378-
self.sleep_time = 0.100
379-
379+
def check_sigwait(self, wait_func):
380380
signum = signal.SIGUSR1
381381
pid = os.getpid()
382382

383383
old_handler = signal.signal(signum, lambda *args: None)
384384
self.addCleanup(signal.signal, signum, old_handler)
385385

386-
rpipe, wpipe = os.pipe()
387-
388386
code = '\n'.join((
389387
'import os, time',
390388
'pid = %s' % os.getpid(),
391389
'signum = %s' % int(signum),
392390
'sleep_time = %r' % self.sleep_time,
393-
'rpipe = %r' % rpipe,
394-
'os.read(rpipe, 1)',
395-
'os.close(rpipe)',
396391
'time.sleep(sleep_time)',
397392
'os.kill(pid, signum)',
398393
))
399394

395+
old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
396+
self.addCleanup(signal.pthread_sigmask, signal.SIG_UNBLOCK, [signum])
397+
400398
t0 = time.monotonic()
401-
proc = self.subprocess(code, pass_fds=(rpipe,))
402-
os.close(rpipe)
399+
proc = self.subprocess(code)
403400
with kill_on_error(proc):
404-
# sync child-parent
405-
os.write(wpipe, b'x')
406-
os.close(wpipe)
401+
wait_func(signum)
402+
dt = time.monotonic() - t0
403+
404+
self.assertEqual(proc.wait(), 0)
407405

408-
# parent
406+
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
407+
'need signal.sigwaitinfo()')
408+
def test_sigwaitinfo(self):
409+
def wait_func(signum):
409410
signal.sigwaitinfo([signum])
410-
dt = time.monotonic() - t0
411-
self.assertEqual(proc.wait(), 0)
412411

413-
self.assertGreaterEqual(dt, self.sleep_time)
412+
self.check_sigwait(wait_func)
413+
414+
@unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
415+
'need signal.sigwaitinfo()')
416+
def test_sigtimedwait(self):
417+
def wait_func(signum):
418+
signal.sigtimedwait([signum], 120.0)
419+
420+
self.check_sigwait(wait_func)
414421

415422

416423
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")

0 commit comments

Comments
 (0)