[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

``