Skip to content

Commit 211a392

Browse files
authored
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).
1 parent 291557e commit 211a392

File tree

1 file changed

+26
-35
lines changed

1 file changed

+26
-35
lines changed

Lib/test/eintrdata/eintr_tester.py

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -371,64 +371,55 @@ def test_sleep(self):
371371

372372

373373
@unittest.skipUnless(hasattr(signal, "setitimer"), "requires setitimer()")
374+
# bpo-30320: Need pthread_sigmask() to block the signal, otherwise the test
375+
# is vulnerable to a race condition between the child and the parent processes.
376+
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
377+
'need signal.pthread_sigmask()')
374378
class SignalEINTRTest(EINTRBaseTest):
375379
""" EINTR tests for the signal module. """
376380

377-
@unittest.skipUnless(hasattr(signal, 'sigtimedwait'),
378-
'need signal.sigtimedwait()')
379-
def test_sigtimedwait(self):
380-
t0 = time.monotonic()
381-
signal.sigtimedwait([signal.SIGUSR1], self.sleep_time)
382-
dt = time.monotonic() - t0
383-
384-
if sys.platform.startswith('aix'):
385-
# On AIX, sigtimedwait(0.2) sleeps 199.8 ms
386-
self.assertGreaterEqual(dt, self.sleep_time * 0.9)
387-
else:
388-
self.assertGreaterEqual(dt, self.sleep_time)
389-
390-
@unittest.skipUnless(hasattr(signal, 'sigwaitinfo'),
391-
'need signal.sigwaitinfo()')
392-
def test_sigwaitinfo(self):
393-
# Issue #25277, #25868: give a few milliseconds to the parent process
394-
# between os.write() and signal.sigwaitinfo() to works around a race
395-
# condition
396-
self.sleep_time = 0.100
397-
381+
def check_sigwait(self, wait_func):
398382
signum = signal.SIGUSR1
399383
pid = os.getpid()
400384

401385
old_handler = signal.signal(signum, lambda *args: None)
402386
self.addCleanup(signal.signal, signum, old_handler)
403387

404-
rpipe, wpipe = os.pipe()
405-
406388
code = '\n'.join((
407389
'import os, time',
408390
'pid = %s' % os.getpid(),
409391
'signum = %s' % int(signum),
410392
'sleep_time = %r' % self.sleep_time,
411-
'rpipe = %r' % rpipe,
412-
'os.read(rpipe, 1)',
413-
'os.close(rpipe)',
414393
'time.sleep(sleep_time)',
415394
'os.kill(pid, signum)',
416395
))
417396

397+
old_mask = signal.pthread_sigmask(signal.SIG_BLOCK, [signum])
398+
self.addCleanup(signal.pthread_sigmask, signal.SIG_UNBLOCK, [signum])
399+
418400
t0 = time.monotonic()
419-
proc = self.subprocess(code, pass_fds=(rpipe,))
420-
os.close(rpipe)
401+
proc = self.subprocess(code)
421402
with kill_on_error(proc):
422-
# sync child-parent
423-
os.write(wpipe, b'x')
424-
os.close(wpipe)
403+
wait_func(signum)
404+
dt = time.monotonic() - t0
405+
406+
self.assertEqual(proc.wait(), 0)
425407

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

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

433424

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

0 commit comments

Comments
 (0)