Skip to content

Commit 88b45b9

Browse files
committed
pythongh-126434: Propagate sys.exit from signal handler to main thread
1 parent 25f4c7e commit 88b45b9

File tree

2 files changed

+41
-29
lines changed

2 files changed

+41
-29
lines changed

Lib/signal.py

+31-26
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import _signal
2+
import sys
23
from _signal import *
34
from enum import IntEnum as _IntEnum
45
import threading
@@ -46,6 +47,7 @@ def _enum_to_int(value):
4647
return value
4748

4849
_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+
_sys_exit_queue = queue.SimpleQueue()
4951
_signal_thread = None
5052
_signo_to_handler = {}
5153

@@ -59,8 +61,12 @@ def _init_signal_thread():
5961

6062
def _push_signal_to_queue_handler(signo, _stack_frame):
6163
assert threading.current_thread() is threading.main_thread()
62-
global _signal_queue
63-
_signal_queue.put(signo)
64+
global _signal_queue, _sys_exit_queue
65+
try:
66+
exit_code = _sys_exit_queue.get(block=False)
67+
sys.exit(exit_code)
68+
except queue.Empty:
69+
_signal_queue.put(signo)
6470

6571
def _sigint_to_str(signo):
6672
for x in valid_signals():
@@ -85,30 +91,29 @@ def stop_signal_thread():
8591
_signal_thread = None
8692

8793
def _signal_queue_handler():
88-
try:
89-
assert threading.current_thread() is not threading.main_thread()
90-
global _signal_queue, _signo_to_handler
91-
while True:
92-
signo = _signal_queue.get()
93-
if signo == 'STOP_SIGNAL_HANDLER':
94-
break
95-
try:
96-
handler = _signo_to_handler.get(signo, None)
97-
if handler is not None:
98-
handler(signo, None)
99-
else:
100-
_log_missing_signal_handler(signo)
101-
except Exception:
102-
traceback.print_exc()
103-
except SystemExit:
104-
pass # TODO: what should be done in the event of a handler calling `sys.exit()`?
105-
except:
106-
traceback.print_exc()
107-
# import _thread
108-
# _thread.interrupt_main()
109-
# print(dir(threading.main_thread()))
110-
finally:
111-
pass
94+
assert threading.current_thread() is not threading.main_thread()
95+
global _signal_queue, _signo_to_handler
96+
while True:
97+
signo = _signal_queue.get()
98+
if signo == 'STOP_SIGNAL_HANDLER':
99+
break
100+
raise_systemexit = False
101+
exitcode = 'NOTSET'
102+
try:
103+
handler = _signo_to_handler.get(signo, None)
104+
if handler is not None:
105+
handler(signo, None)
106+
else:
107+
_log_missing_signal_handler(signo)
108+
except SystemExit as se:
109+
exitcode = se.code
110+
raise_systemexit = True
111+
except Exception:
112+
traceback.print_exc()
113+
if raise_systemexit:
114+
global _sys_exit_queue
115+
_sys_exit_queue.put(exitcode)
116+
raise_signal(signo)
112117

113118
# Similar to functools.wraps(), but only assign __doc__.
114119
# __module__ should be preserved,

bug.py

+10-3
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,24 @@ def run_signal_handler_dedicated_thread():
1919
event = multiprocessing.Event()
2020
def sigint_handler(_signo, _stack_frame):
2121
try:
22-
print(f'{threading.current_thread().name}: sigint_handler is setting event')
23-
#event.set()
22+
# print(f'{threading.current_thread().name}: sigint_handler raising SIGUSR1 ...')
23+
# signal.raise_signal(signal.SIGUSR1)
24+
# print(f'{threading.current_thread().name}: sigint_handler raising SIGUSR1 ... OK')
2425
sys.exit()
2526
finally:
26-
print(f'{threading.current_thread().name}: sigint_handler is done')
27+
print(f'{threading.current_thread().name}: sigint_handler exiting ...')
28+
29+
def sigusr1_handler(_signo, _):
30+
print(f'{threading.current_thread().name}: USR1 running')
2731

2832
def sigterm_handler(_signo, _stack_frame):
2933
print(f'{threading.current_thread().name}: sigterm_handler is running')
3034
pass
3135

3236
signal.signal(signal.SIGINT, sigint_handler, True)
37+
signal.signal(signal.SIGUSR1, sigusr1_handler, True)
38+
39+
# signal.raise_signal(signal.SIGINT)
3340

3441
threading.Thread(target=sigint_self, daemon=True).start()
3542
threading.Thread(target=sigkill_self, daemon=True).start() # Used for debugging only.

0 commit comments

Comments
 (0)