[3.6] bpo-30703: Improve signal delivery (GH-2415) (#2527) · python/cpython@3024c05 (original) (raw)
`@@ -3,11 +3,13 @@
`
3
3
`from contextlib import closing
`
4
4
`import enum
`
5
5
`import gc
`
``
6
`+
import os
`
6
7
`import pickle
`
``
8
`+
import random
`
7
9
`import select
`
8
10
`import signal
`
9
11
`import socket
`
10
``
`-
import struct
`
``
12
`+
import statistics
`
11
13
`import subprocess
`
12
14
`import traceback
`
13
15
`import sys, os, time, errno
`
`@@ -955,6 +957,135 @@ def handler(signum, frame):
`
955
957
` (exitcode, stdout))
`
956
958
``
957
959
``
``
960
`+
class StressTest(unittest.TestCase):
`
``
961
`+
"""
`
``
962
`+
Stress signal delivery, especially when a signal arrives in
`
``
963
`+
the middle of recomputing the signal state or executing
`
``
964
`+
previously tripped signal handlers.
`
``
965
`+
"""
`
``
966
+
``
967
`+
def setsig(self, signum, handler):
`
``
968
`+
old_handler = signal.signal(signum, handler)
`
``
969
`+
self.addCleanup(signal.signal, signum, old_handler)
`
``
970
+
``
971
`+
def measure_itimer_resolution(self):
`
``
972
`+
N = 20
`
``
973
`+
times = []
`
``
974
+
``
975
`+
def handler(signum=None, frame=None):
`
``
976
`+
if len(times) < N:
`
``
977
`+
times.append(time.perf_counter())
`
``
978
`+
1 µs is the smallest possible timer interval,
`
``
979
`+
we want to measure what the concrete duration
`
``
980
`+
will be on this platform
`
``
981
`+
signal.setitimer(signal.ITIMER_REAL, 1e-6)
`
``
982
+
``
983
`+
self.addCleanup(signal.setitimer, signal.ITIMER_REAL, 0)
`
``
984
`+
self.setsig(signal.SIGALRM, handler)
`
``
985
`+
handler()
`
``
986
`+
while len(times) < N:
`
``
987
`+
time.sleep(1e-3)
`
``
988
+
``
989
`+
durations = [times[i+1] - times[i] for i in range(len(times) - 1)]
`
``
990
`+
med = statistics.median(durations)
`
``
991
`+
if support.verbose:
`
``
992
`+
print("detected median itimer() resolution: %.6f s." % (med,))
`
``
993
`+
return med
`
``
994
+
``
995
`+
def decide_itimer_count(self):
`
``
996
`+
Some systems have poor setitimer() resolution (for example
`
``
997
`+
measured around 20 ms. on FreeBSD 9), so decide on a reasonable
`
``
998
`+
number of sequential timers based on that.
`
``
999
`+
reso = self.measure_itimer_resolution()
`
``
1000
`+
if reso <= 1e-4:
`
``
1001
`+
return 10000
`
``
1002
`+
elif reso <= 1e-2:
`
``
1003
`+
return 100
`
``
1004
`+
else:
`
``
1005
`+
self.skipTest("detected itimer resolution (%.3f s.) too high "
`
``
1006
`+
"(> 10 ms.) on this platform (or system too busy)"
`
``
1007
`+
% (reso,))
`
``
1008
+
``
1009
`+
@unittest.skipUnless(hasattr(signal, "setitimer"),
`
``
1010
`+
"test needs setitimer()")
`
``
1011
`+
def test_stress_delivery_dependent(self):
`
``
1012
`+
"""
`
``
1013
`+
This test uses dependent signal handlers.
`
``
1014
`+
"""
`
``
1015
`+
N = self.decide_itimer_count()
`
``
1016
`+
sigs = []
`
``
1017
+
``
1018
`+
def first_handler(signum, frame):
`
``
1019
`` +
1e-6 is the minimum non-zero value for setitimer()
.
``
``
1020
`+
Choose a random delay so as to improve chances of
`
``
1021
`+
triggering a race condition. Ideally the signal is received
`
``
1022
`+
when inside critical signal-handling routines such as
`
``
1023
`+
Py_MakePendingCalls().
`
``
1024
`+
signal.setitimer(signal.ITIMER_REAL, 1e-6 + random.random() * 1e-5)
`
``
1025
+
``
1026
`+
def second_handler(signum=None, frame=None):
`
``
1027
`+
sigs.append(signum)
`
``
1028
+
``
1029
`+
Here on Linux, SIGPROF > SIGALRM > SIGUSR1. By using both
`
``
1030
`+
ascending and descending sequences (SIGUSR1 then SIGALRM,
`
``
1031
`+
SIGPROF then SIGALRM), we maximize chances of hitting a bug.
`
``
1032
`+
self.setsig(signal.SIGPROF, first_handler)
`
``
1033
`+
self.setsig(signal.SIGUSR1, first_handler)
`
``
1034
`+
self.setsig(signal.SIGALRM, second_handler) # for ITIMER_REAL
`
``
1035
+
``
1036
`+
expected_sigs = 0
`
``
1037
`+
deadline = time.time() + 15.0
`
``
1038
+
``
1039
`+
while expected_sigs < N:
`
``
1040
`+
os.kill(os.getpid(), signal.SIGPROF)
`
``
1041
`+
expected_sigs += 1
`
``
1042
`+
Wait for handlers to run to avoid signal coalescing
`
``
1043
`+
while len(sigs) < expected_sigs and time.time() < deadline:
`
``
1044
`+
time.sleep(1e-5)
`
``
1045
+
``
1046
`+
os.kill(os.getpid(), signal.SIGUSR1)
`
``
1047
`+
expected_sigs += 1
`
``
1048
`+
while len(sigs) < expected_sigs and time.time() < deadline:
`
``
1049
`+
time.sleep(1e-5)
`
``
1050
+
``
1051
`+
All ITIMER_REAL signals should have been delivered to the
`
``
1052
`+
Python handler
`
``
1053
`+
self.assertEqual(len(sigs), N, "Some signals were lost")
`
``
1054
+
``
1055
`+
@unittest.skipUnless(hasattr(signal, "setitimer"),
`
``
1056
`+
"test needs setitimer()")
`
``
1057
`+
def test_stress_delivery_simultaneous(self):
`
``
1058
`+
"""
`
``
1059
`+
This test uses simultaneous signal handlers.
`
``
1060
`+
"""
`
``
1061
`+
N = self.decide_itimer_count()
`
``
1062
`+
sigs = []
`
``
1063
+
``
1064
`+
def handler(signum, frame):
`
``
1065
`+
sigs.append(signum)
`
``
1066
+
``
1067
`+
self.setsig(signal.SIGUSR1, handler)
`
``
1068
`+
self.setsig(signal.SIGALRM, handler) # for ITIMER_REAL
`
``
1069
+
``
1070
`+
expected_sigs = 0
`
``
1071
`+
deadline = time.time() + 15.0
`
``
1072
+
``
1073
`+
while expected_sigs < N:
`
``
1074
`+
Hopefully the SIGALRM will be received somewhere during
`
``
1075
`+
initial processing of SIGUSR1.
`
``
1076
`+
signal.setitimer(signal.ITIMER_REAL, 1e-6 + random.random() * 1e-5)
`
``
1077
`+
os.kill(os.getpid(), signal.SIGUSR1)
`
``
1078
+
``
1079
`+
expected_sigs += 2
`
``
1080
`+
Wait for handlers to run to avoid signal coalescing
`
``
1081
`+
while len(sigs) < expected_sigs and time.time() < deadline:
`
``
1082
`+
time.sleep(1e-5)
`
``
1083
+
``
1084
`+
All ITIMER_REAL signals should have been delivered to the
`
``
1085
`+
Python handler
`
``
1086
`+
self.assertEqual(len(sigs), N, "Some signals were lost")
`
``
1087
+
``
1088
+
958
1089
`def tearDownModule():
`
959
1090
`support.reap_children()
`
960
1091
``