Skip to content

Commit 5a43697

Browse files
committed
pythongh-126434: Continue process signals on dedicated thread, don't bubble exception on main thread
1 parent c308344 commit 5a43697

File tree

5 files changed

+99
-110
lines changed

5 files changed

+99
-110
lines changed

Lib/signal.py

+54-31
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import _signal
2-
import os
32
from _signal import *
43
from enum import IntEnum as _IntEnum
54
import threading
65
import queue
6+
import traceback
77

88
_globals = globals()
99

@@ -45,43 +45,65 @@ def _enum_to_int(value):
4545
except (ValueError, TypeError):
4646
return value
4747

48-
_init_lock = threading.Lock()
4948
_signal_queue = queue.SimpleQueue() # SimpleQueue has reentrant put, so it can safely be called from signal handlers. https://github.com/python/cpython/issues/59181
50-
_bubble_queue = queue.SimpleQueue()
5149
_signal_thread = None
5250
_signo_to_handler = {}
5351

54-
def _signal_queue_handler():
55-
assert threading.current_thread() is not threading.main_thread()
56-
global _signal_queue, _signo_to_handler, _bubble_queue
57-
while True:
58-
(signo, stack_frame) = _signal_queue.get()
59-
try:
60-
handler = _signo_to_handler.get(signo, None)
61-
handler(signo, stack_frame)
62-
except Exception as e:
63-
_bubble_queue.put(e)
64-
# _signal.raise_signal(SIGTERM) # does not work when using event.wait()
65-
# _thread.interrupt_main(SIGTERM) # does not work when using event.wait()
66-
os.kill(os.getpid(), signo)
67-
6852
def _init_signal_thread():
6953
assert threading.current_thread() is threading.main_thread()
70-
global _signal_thread, _init_lock
71-
with _init_lock:
72-
if _signal_thread is None:
73-
_signal_thread = threading.Thread(target=_signal_queue_handler, daemon=True)
74-
_signal_thread.start()
54+
global _signal_thread
55+
if _signal_thread is None:
56+
_signal_thread = threading.Thread(target=_signal_queue_handler, daemon=True)
57+
_signal_thread.name = 'SignalHandlerThread'
58+
_signal_thread.start()
7559

76-
def _push_signal_to_queue_handler(signo, stack_frame):
60+
def _push_signal_to_queue_handler(signo, _stack_frame):
7761
assert threading.current_thread() is threading.main_thread()
62+
global _signal_queue
63+
_signal_queue.put(signo)
64+
65+
def _sigint_to_str(signo):
66+
for x in valid_signals():
67+
if x == signo:
68+
return x.name
69+
raise RuntimeError('Could not find signal name')
70+
71+
def _log_missing_signal_handler(signo):
72+
import logging
73+
logger = logging.getLogger(__name__)
74+
str_name = ''
75+
for x in valid_signals():
76+
if x == signo:
77+
str_name = x.name
78+
logger.warning('Handler for signal.%s (%d) was not found.', str_name, signo)
79+
80+
def _stop_signal_thread():
81+
global _signal_thread, _signal_queue
82+
if _signal_thread is not None:
83+
_signal_queue.put('STOP_SIGNAL_HANDLER')
84+
85+
def _signal_queue_handler():
7886
try:
79-
global _bubble_queue
80-
bubble_exception = _bubble_queue.get(block=False)
81-
raise bubble_exception
82-
except queue.Empty:
83-
global _signal_queue
84-
_signal_queue.put((signo, stack_frame))
87+
assert threading.current_thread() is not threading.main_thread()
88+
global _signal_queue, _signo_to_handler
89+
while True:
90+
signo = _signal_queue.get()
91+
if signo == 'STOP_SIGNAL_HANDLER':
92+
break
93+
try:
94+
handler = _signo_to_handler.get(signo, None)
95+
if handler is not None:
96+
handler(signo, None)
97+
else:
98+
_log_missing_signal_handler(signo)
99+
except Exception:
100+
traceback.print_exc()
101+
pass
102+
except:
103+
pass
104+
finally:
105+
global _signal_thread
106+
_signal_thread = None
85107

86108
# Similar to functools.wraps(), but only assign __doc__.
87109
# __module__ should be preserved,
@@ -93,8 +115,9 @@ def decorator(wrapper):
93115
return wrapper
94116
return decorator
95117

96-
def signal(signalnum, handler, use_dedicated_thread=True):
97-
assert threading.current_thread() is threading.main_thread()
118+
def signal(signalnum, handler, use_dedicated_thread=False):
119+
if use_dedicated_thread:
120+
assert threading.current_thread() is threading.main_thread()
98121
global _signo_to_handler
99122
signal_int = _enum_to_int(signalnum)
100123
old_handler = _signo_to_handler.get(signal_int, None)

Lib/test/test_multiprocessing_event/__init__.py

-5
This file was deleted.

Lib/test/test_multiprocessing_event/test_event.py

-73
This file was deleted.

Makefile.pre.in

-1
Original file line numberDiff line numberDiff line change
@@ -2520,7 +2520,6 @@ TESTSUBDIRS= idlelib/idle_test \
25202520
test/test_interpreters \
25212521
test/test_json \
25222522
test/test_module \
2523-
test/test_multiprocessing_event \
25242523
test/test_multiprocessing_fork \
25252524
test/test_multiprocessing_forkserver \
25262525
test/test_multiprocessing_spawn \

bug.py

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import os
2+
import signal
3+
import threading
4+
import time
5+
import multiprocessing
6+
7+
def sigint_self():
8+
time.sleep(1)
9+
print(f'{threading.current_thread().name}: Stopping PID {os.getpid()} with SIGINT')
10+
os.kill(os.getpid(), signal.SIGINT)
11+
12+
def sigkill_self():
13+
time.sleep(5)
14+
print(f'{threading.current_thread().name}: Killing PID {os.getpid()} with SIGKILL')
15+
os.kill(os.getpid(), signal.SIGKILL)
16+
17+
def run_signal_handler_dedicated_thread():
18+
event = multiprocessing.Event()
19+
def sigint_handler(_signo, _stack_frame):
20+
try:
21+
x = 1 / 0
22+
print(f'{threading.current_thread().name}: sigint_handler is setting event')
23+
event.set()
24+
finally:
25+
print(f'{threading.current_thread().name}: sigint_handler is done')
26+
27+
def sigterm_handler(_signo, _stack_frame):
28+
print(f'{threading.current_thread().name}: sigterm_handler is running')
29+
pass
30+
31+
signal.signal(signal.SIGTERM, sigterm_handler)
32+
signal.signal(signal.SIGINT, sigint_handler)
33+
34+
threading.Thread(target=sigint_self, daemon=True).start()
35+
threading.Thread(target=sigkill_self, daemon=True).start() # Used for debugging only.
36+
37+
print(f'{threading.current_thread().name}: Waiting on event. PID = {os.getpid()}')
38+
event.wait()
39+
print(f'{threading.current_thread().name}: Waiting is done')
40+
41+
if __name__ == '__main__':
42+
try:
43+
run_signal_handler_dedicated_thread()
44+
finally:
45+
print(f'{threading.current_thread().name}: Exiting')

0 commit comments

Comments
 (0)