Skip to content

Commit

Permalink
pythongh-126434: Propagate sys.exit from signal handler to main thread
Browse files Browse the repository at this point in the history
  • Loading branch information
ivarref committed Dec 3, 2024
1 parent 25f4c7e commit 88b45b9
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 29 deletions.
57 changes: 31 additions & 26 deletions Lib/signal.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import _signal
import sys
from _signal import *
from enum import IntEnum as _IntEnum
import threading
Expand Down Expand Up @@ -46,6 +47,7 @@ def _enum_to_int(value):
return value

_signal_queue = queue.SimpleQueue() # SimpleQueue has reentrant put, so it can safely be called from signal handlers. https://github.com/python/cpython/issues/59181
_sys_exit_queue = queue.SimpleQueue()
_signal_thread = None
_signo_to_handler = {}

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

def _push_signal_to_queue_handler(signo, _stack_frame):
assert threading.current_thread() is threading.main_thread()
global _signal_queue
_signal_queue.put(signo)
global _signal_queue, _sys_exit_queue
try:
exit_code = _sys_exit_queue.get(block=False)
sys.exit(exit_code)
except queue.Empty:
_signal_queue.put(signo)

def _sigint_to_str(signo):
for x in valid_signals():
Expand All @@ -85,30 +91,29 @@ def stop_signal_thread():
_signal_thread = None

def _signal_queue_handler():
try:
assert threading.current_thread() is not threading.main_thread()
global _signal_queue, _signo_to_handler
while True:
signo = _signal_queue.get()
if signo == 'STOP_SIGNAL_HANDLER':
break
try:
handler = _signo_to_handler.get(signo, None)
if handler is not None:
handler(signo, None)
else:
_log_missing_signal_handler(signo)
except Exception:
traceback.print_exc()
except SystemExit:
pass # TODO: what should be done in the event of a handler calling `sys.exit()`?
except:
traceback.print_exc()
# import _thread
# _thread.interrupt_main()
# print(dir(threading.main_thread()))
finally:
pass
assert threading.current_thread() is not threading.main_thread()
global _signal_queue, _signo_to_handler
while True:
signo = _signal_queue.get()
if signo == 'STOP_SIGNAL_HANDLER':
break
raise_systemexit = False
exitcode = 'NOTSET'
try:
handler = _signo_to_handler.get(signo, None)
if handler is not None:
handler(signo, None)
else:
_log_missing_signal_handler(signo)
except SystemExit as se:
exitcode = se.code
raise_systemexit = True
except Exception:
traceback.print_exc()
if raise_systemexit:
global _sys_exit_queue
_sys_exit_queue.put(exitcode)
raise_signal(signo)

# Similar to functools.wraps(), but only assign __doc__.
# __module__ should be preserved,
Expand Down
13 changes: 10 additions & 3 deletions bug.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ def run_signal_handler_dedicated_thread():
event = multiprocessing.Event()
def sigint_handler(_signo, _stack_frame):
try:
print(f'{threading.current_thread().name}: sigint_handler is setting event')
#event.set()
# print(f'{threading.current_thread().name}: sigint_handler raising SIGUSR1 ...')
# signal.raise_signal(signal.SIGUSR1)
# print(f'{threading.current_thread().name}: sigint_handler raising SIGUSR1 ... OK')
sys.exit()
finally:
print(f'{threading.current_thread().name}: sigint_handler is done')
print(f'{threading.current_thread().name}: sigint_handler exiting ...')

def sigusr1_handler(_signo, _):
print(f'{threading.current_thread().name}: USR1 running')

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

signal.signal(signal.SIGINT, sigint_handler, True)
signal.signal(signal.SIGUSR1, sigusr1_handler, True)

# signal.raise_signal(signal.SIGINT)

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

0 comments on commit 88b45b9

Please sign in to comment.