Skip to content

bpo-30796: Fix failures in signal delivery stress test #2488

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 29, 2017
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 50 additions & 15 deletions Lib/test/test_signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import random
import signal
import socket
import statistics
import subprocess
import sys
import time
Expand Down Expand Up @@ -949,13 +950,55 @@ class StressTest(unittest.TestCase):
previously tripped signal handlers.
"""

def setsig(self, signum, handler):
old_handler = signal.signal(signum, handler)
self.addCleanup(signal.signal, signum, old_handler)

def measure_itimer_resolution(self):
N = 20
times = []

def handler(signum=None, frame=None):
if len(times) < N:
times.append(time.perf_counter())
# 1 µs is the smallest possible timer interval,
# we want to measure what the concrete duration
# will be on this platform
signal.setitimer(signal.ITIMER_REAL, 1e-6)

self.addCleanup(signal.setitimer, signal.ITIMER_REAL, 0)
self.setsig(signal.SIGALRM, handler)
handler()
while len(times) < N:
time.sleep(1e-3)

durations = [times[i+1] - times[i] for i in range(len(times) - 1)]
med = statistics.median(durations)
if support.verbose:
print("detected median itimer() resolution: %.6f s." % (med,))
return med

def decide_itimer_count(self):
# Some systems have poor setitimer() resolution (for example
# measured around 20 ms. on FreeBSD 9), so decide on a reasonable
# number of sequential timers based on that.
reso = self.measure_itimer_resolution()
if reso <= 1e-4:
return 10000
elif reso <= 1e-2:
return 100
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't 100 enough on all platforms?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10000 triggers the issue much more reliably in my tests (it's very subtle).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"10000 triggers the issue much more reliably in my tests (it's very subtle)." ok

else:
self.skipTest("detected itimer resolution (%.3f s.) too high "
"(> 10 ms.) on this platform (or system too busy)"
% (reso,))

@unittest.skipUnless(hasattr(signal, "setitimer"),
"test needs setitimer()")
def test_stress_delivery_dependent(self):
"""
This test uses dependent signal handlers.
"""
N = 10000
N = self.decide_itimer_count()
sigs = []

def first_handler(signum, frame):
Expand All @@ -969,16 +1012,12 @@ def first_handler(signum, frame):
def second_handler(signum=None, frame=None):
sigs.append(signum)

def setsig(signum, handler):
old_handler = signal.signal(signum, handler)
self.addCleanup(signal.signal, signum, old_handler)

# Here on Linux, SIGPROF > SIGALRM > SIGUSR1. By using both
# ascending and descending sequences (SIGUSR1 then SIGALRM,
# SIGPROF then SIGALRM), we maximize chances of hitting a bug.
setsig(signal.SIGPROF, first_handler)
setsig(signal.SIGUSR1, first_handler)
setsig(signal.SIGALRM, second_handler) # for ITIMER_REAL
self.setsig(signal.SIGPROF, first_handler)
self.setsig(signal.SIGUSR1, first_handler)
self.setsig(signal.SIGALRM, second_handler) # for ITIMER_REAL

expected_sigs = 0
deadline = time.time() + 15.0
Expand All @@ -1005,18 +1044,14 @@ def test_stress_delivery_simultaneous(self):
"""
This test uses simultaneous signal handlers.
"""
N = 10000
N = self.decide_itimer_count()
sigs = []

def handler(signum, frame):
sigs.append(signum)

def setsig(signum, handler):
old_handler = signal.signal(signum, handler)
self.addCleanup(signal.signal, signum, old_handler)

setsig(signal.SIGUSR1, handler)
setsig(signal.SIGALRM, handler) # for ITIMER_REAL
self.setsig(signal.SIGUSR1, handler)
self.setsig(signal.SIGALRM, handler) # for ITIMER_REAL

expected_sigs = 0
deadline = time.time() + 15.0
Expand Down