@@ -46,6 +46,9 @@ This has consequences:
4646 arbitrary amount of time, regardless of any signals received. The Python
4747 signal handlers will be called when the calculation finishes.
4848
49+ * If the handler raises an exception, it will be raised "out of thin air" in
50+ the main thread. See the :ref: `note below <handlers-and-exceptions >` for a
51+ discussion.
4952
5053.. _signals-and-threads :
5154
@@ -712,3 +715,70 @@ Do not set :const:`SIGPIPE`'s disposition to :const:`SIG_DFL`
712715in order to avoid :exc: `BrokenPipeError `. Doing that would cause
713716your program to exit unexpectedly also whenever any socket connection
714717is interrupted while your program is still writing to it.
718+
719+ .. _handlers-and-exceptions :
720+
721+ Note on Signal Handlers and Exceptions
722+ --------------------------------------
723+
724+ If a signal handler raises an exception, the exception will be propagated to
725+ the main thread and may be raised after any :term: `bytecode ` instruction. Most
726+ notably, a :exc: `KeyboardInterrupt ` may appear at any point during execution.
727+ Most Python code, including the standard library, cannot be made robust against
728+ this, and so a :exc: `KeyboardInterrupt ` (or any other exception resulting from
729+ a signal handler) may on rare occasions put the program in an unexpected state.
730+
731+ To illustrate this issue, consider the following code::
732+
733+ class SpamContext:
734+ def __init__(self):
735+ self.lock = threading.Lock()
736+
737+ def __enter__(self):
738+ # If KeyboardInterrupt occurs here, everything is fine
739+ self.lock.acquire()
740+ # If KeyboardInterrupt occcurs here, __exit__ will not be called
741+ ...
742+ # KeyboardInterrupt could occur just before the function returns
743+
744+ def __exit__(self, exc_type, exc_val, exc_tb):
745+ ...
746+ self.lock.release()
747+
748+ For many programs, especially those that merely want to exit on
749+ :exc: `KeyboardInterrupt `, this is not a problem, but applications that are
750+ complex or require high reliability should avoid raising exceptions from signal
751+ handlers. They should also avoid catching :exc: `KeyboardInterrupt ` as a means
752+ of gracefully shutting down. Instead, they should install their own
753+ :const: `SIGINT ` handler. Below is an example of an HTTP server that avoids
754+ :exc: `KeyboardInterrupt `::
755+
756+ import signal
757+ import socket
758+ from selectors import DefaultSelector, EVENT_READ
759+ from http.server import HTTPServer, SimpleHTTPRequestHandler
760+
761+ interrupt_read, interrupt_write = socket.socketpair()
762+
763+ def handler(signum, frame):
764+ print('Signal handler called with signal', signum)
765+ interrupt_write.send(b'\0')
766+ signal.signal(signal.SIGINT, handler)
767+
768+ def serve_forever(httpd):
769+ sel = DefaultSelector()
770+ sel.register(interrupt_read, EVENT_READ)
771+ sel.register(httpd, EVENT_READ)
772+
773+ while True:
774+ for key, _ in sel.select():
775+ if key.fileobj == interrupt_read:
776+ interrupt_read.recv(1)
777+ return
778+ if key.fileobj == httpd:
779+ httpd.handle_request()
780+
781+ print("Serving on port 8000")
782+ httpd = HTTPServer(('', 8000), SimpleHTTPRequestHandler)
783+ serve_forever(httpd)
784+ print("Shutdown...")
0 commit comments