Skip to content

Commit

Permalink
pythongh-126434: What should be done if sys.exit() is called from a s…
Browse files Browse the repository at this point in the history
…ignal handler (on a non-main thread)?
  • Loading branch information
ivarref committed Dec 3, 2024
1 parent 574e91c commit 25f4c7e
Show file tree
Hide file tree
Showing 4 changed files with 24 additions and 9 deletions.
15 changes: 11 additions & 4 deletions Lib/signal.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ def stop_signal_thread():
global _signal_thread, _signal_queue
if _signal_thread is not None:
_signal_queue.put('STOP_SIGNAL_HANDLER')
_signal_thread.join()
_signal_thread = None

def _signal_queue_handler():
try:
Expand All @@ -98,12 +100,15 @@ def _signal_queue_handler():
_log_missing_signal_handler(signo)
except Exception:
traceback.print_exc()
pass
except SystemExit:
pass # TODO: what should be done in the event of a handler calling `sys.exit()`?
except:
pass
traceback.print_exc()
# import _thread
# _thread.interrupt_main()
# print(dir(threading.main_thread()))
finally:
global _signal_thread
_signal_thread = None
pass

# Similar to functools.wraps(), but only assign __doc__.
# __module__ should be preserved,
Expand Down Expand Up @@ -132,6 +137,8 @@ def signal(signalnum, handler, use_dedicated_thread=True):
else:
if signal_int in _signo_to_handler:
del _signo_to_handler[signal_int]
if 0 == len(_signo_to_handler):
stop_signal_thread()
handler = _signal.signal(signal_int, _enum_to_int(handler))
return old_handler or _int_to_enum(handler, Handlers)

Expand Down
3 changes: 3 additions & 0 deletions Lib/test/support/threading_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import threading
import time
import unittest
import signal

from test import support

Expand All @@ -28,6 +29,8 @@ def threading_setup():
def threading_cleanup(*original_values):
orig_count, orig_ndangling = original_values

signal.stop_signal_thread()

timeout = 1.0
for _ in support.sleeping_retry(timeout, error=False):
# Copy the thread list to get a consistent output. threading._dangling
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_asyncio/test_unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

def tearDownModule():
asyncio.set_event_loop_policy(None)
signal.stop_signal_thread()


MOCK_ANY = mock.ANY
Expand Down
14 changes: 9 additions & 5 deletions bug.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import threading
import time
import multiprocessing
import sys

def sigint_self():
time.sleep(1)
Expand All @@ -18,24 +19,27 @@ def run_signal_handler_dedicated_thread():
event = multiprocessing.Event()
def sigint_handler(_signo, _stack_frame):
try:
x = 1 / 0
print(f'{threading.current_thread().name}: sigint_handler is setting event')
event.set()
#event.set()
sys.exit()
finally:
print(f'{threading.current_thread().name}: sigint_handler is done')

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

signal.signal(signal.SIGTERM, sigterm_handler)
signal.signal(signal.SIGINT, sigint_handler)
signal.signal(signal.SIGINT, sigint_handler, True)

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

print(f'{threading.current_thread().name}: Waiting on event. PID = {os.getpid()}')
event.wait()
while True:
if event.is_set():
break
else:
time.sleep(0.1)
print(f'{threading.current_thread().name}: Waiting is done')

if __name__ == '__main__':
Expand Down

0 comments on commit 25f4c7e

Please sign in to comment.