Self-pipe, yeah, except in Python you don't have to build it. signal.set_wakeup_fd() is exactly that: hand it an fd (or a socket on Windows) and the interpreter writes the signal number to it. Then you select/poll that fd in your normal loop and do the actual work outside the handler. asyncio uses it under the hood for add_signal_handler.
The other one that plays nicely with threads is blocking the signals everywhere with pthread_sigmask and parking one dedicated thread in sigwait(). Both are in the stdlib on Unix.
signalfd is nicer than either but it's Linux only, which is why set_wakeup_fd usually wins if you care about portability.
Self-pipe, yeah, except in Python you don't have to build it. signal.set_wakeup_fd() is exactly that: hand it an fd (or a socket on Windows) and the interpreter writes the signal number to it. Then you select/poll that fd in your normal loop and do the actual work outside the handler. asyncio uses it under the hood for add_signal_handler.
The other one that plays nicely with threads is blocking the signals everywhere with pthread_sigmask and parking one dedicated thread in sigwait(). Both are in the stdlib on Unix.
signalfd is nicer than either but it's Linux only, which is why set_wakeup_fd usually wins if you care about portability.