Python or not, almost nothing is safe inside a signal handler.
knome 5 hours ago [-]
the first line of the article points out that python isn't run in the POSIX C handler. that just sets a flag for the interpreter to act on. the python issue is an unsafe re-entrant handling strategy in the interpreter.
masklinn 3 hours ago [-]
And this is explained in more details in the first article of the series.
zaphirplane 44 minutes ago [-]
Too funny
krackers 3 hours ago [-]
>almost nothing is safe
That's a bit of an overstatement? There's a list of things that you _can_ call, and fairly useful ones too like `write`
There is also a charming statement in POSIX standard that
If the signal occurs other than as the result of calling abort(), raise(),
[CX] [Option Start] kill(), pthread_kill(), or sigqueue(), [Option End] the
behavior is undefined if the signal handler refers to any object with static
storage duration other than by assigning a value to an object declared as
volatile sig_atomic_t, or if the signal handler calls any function in the
standard library other than one of the functions listed in Signal Concepts.
You literally can't read any global/static variables and you can only write to global/static variables that are declared to be volatile sig_atomic_t. This tremendously shrinks the amount of useful work you can do with the signal-safe functions from the standard library.
nottorp 2 hours ago [-]
It may help if you think of them as interrupts and not something that comes in your message queue.
Joker_vD 2 hours ago [-]
No, I understand that. I just find it deeply ironic that when an interrupt/signal arrives, pretty much the only thing you can do to handle it, is to raise some flag, then leave the handler and continue doing whatever you were doing in a message loop. Like, why even bother with supporting function callbacks in sigaction() etc? Just have each thread have a chunk of volatile memory where the kernel writes info about the arrived signals, and that's it, that's your signal handling framework.
In fact, here is another, a very fresh, example from POSIX: [0]. There is an example at how to use SIGWINCH signal handler with tcgetwinsize(). Just look at this thing of terrible beauty, notice that SIG_ATOMIC_MAX is not required to bigger than a byte's worth of data, and also read the whole of "APPLICATION USAGE" section. "Multi-threaded applications should avoid the signal handler idiom in general", gee, I wonder why. And of course, the signal may never be generated in the first place, so "[s]uch processes must periodically poll the current terminal window size if needed". What a solid technical foundation to build race-free, bug-free applications on top of.
"From boils, mildew and dirt / I've brought forth new beauty and new worth." [1]
Let's call them crippled interrupts. As long as you don't think of them as a message queue...
In their defense, the original signals were there mostly for "handle this or I'll terminate you" conditions. Then stuff got bolted on...
[1] Possibly LLM hallucinated translation from a Romanian poet. Although it did give me a link to a paywalled essay that I couldn't verify.
westurner 40 minutes ago [-]
How would you fix signals and do you propose a complete set of patterns for safe concurrency? And so why is there limited scope for signal handlers?
Should you use signal handlers with eBPF?
inigyou 4 hours ago [-]
Certain signals are true user-mode interrupts, like SIGTERM and some of the ones glibc uses to implement pthreads.
The ones related to application logic must only be handled with signalfd if you want any semblance of reliability.
pjmlp 3 hours ago [-]
Exactly, the only safe thing to do in a signal is set a variable for other code to react on, also they completely mess up in multi-threaded code.
germandiago 3 hours ago [-]
That is exactly what I do: set an stomic variable and do yhings outside of the handler.
IgorPartola 7 hours ago [-]
What’s really fun is mixing signal handling and threads, especially on Linux. There is a simple way to do it and about a thousand ways that include at least one gotcha.
zbentley 5 hours ago [-]
What’s the simple way? Self-pipe?
tyffanypastecf 2 hours ago [-]
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.
jrumbut 5 hours ago [-]
I think the author undersells the significance of this. I could easily picture someone writing code that boils down to the example, all it requires is a flood of signals and a print statement.
If your program generates signals, it could generate a flood unintentionally. If you put a print statement in the handler, you can get this result.
It's not terribly surprising, but good to know.
5 hours ago [-]
Uptrenda 6 hours ago [-]
And when you combine it with event loops and multiple OSes + python versions it gets even more difficult. Don't get me wrong: I love python, but shut down / cleanup is kind of a pain in the ass. If someone built a (good) lib for this it would probably be quite popular.
charcircuit 5 hours ago [-]
I don't understand how this is still a problem in 2026. Signals should just come in via a new thread and it would solve all the complexity around them. Everyone has known the current way it works is extremely limited in what you can safely do. This whole pause the execution of what's currently running and then run some extra code somewhere else turned out to not be a good idea.
inigyou 4 hours ago [-]
They should come in via signalfd unless they're the moral equivalent of a non-maskable interrupt.
shawn_w 4 hours ago [-]
And for people who want to use something other than Linux?
charcircuit 4 hours ago [-]
That is also good, but requires apps be rewritten to read from signalfd. With the separate thread approach you can get away without having to rewrite programs.
time4tea 37 minutes ago [-]
Betteridge
fenestella 5 hours ago [-]
[flagged]
megagpt3 6 hours ago [-]
He considers it safe if it's unlikely to crash? That's also true in C. Calling printf in a C signal handler is likely to work. So why does he consider it important in C but "not a practical consideration" in Python? It's more likely to crash in Python than in C because the signal handler takes longer to execute.
lmz 4 hours ago [-]
The article mentions that the Python handler is run outside of the C handler context and so is not subject to the C safety restrictions. It will not crash since the interpreter only calls the Python handler when it is safe. It will however not protect against reentrancy issues in the Python handler.
Python or not, almost nothing is safe inside a signal handler.
That's a bit of an overstatement? There's a list of things that you _can_ call, and fairly useful ones too like `write`
https://man7.org/linux/man-pages/man7/signal-safety.7.html
In fact, here is another, a very fresh, example from POSIX: [0]. There is an example at how to use SIGWINCH signal handler with tcgetwinsize(). Just look at this thing of terrible beauty, notice that SIG_ATOMIC_MAX is not required to bigger than a byte's worth of data, and also read the whole of "APPLICATION USAGE" section. "Multi-threaded applications should avoid the signal handler idiom in general", gee, I wonder why. And of course, the signal may never be generated in the first place, so "[s]uch processes must periodically poll the current terminal window size if needed". What a solid technical foundation to build race-free, bug-free applications on top of.
[0] https://pubs.opengroup.org/onlinepubs/9799919799/functions/t...
Let's call them crippled interrupts. As long as you don't think of them as a message queue...
In their defense, the original signals were there mostly for "handle this or I'll terminate you" conditions. Then stuff got bolted on...
[1] Possibly LLM hallucinated translation from a Romanian poet. Although it did give me a link to a paywalled essay that I couldn't verify.
Should you use signal handlers with eBPF?
The ones related to application logic must only be handled with signalfd if you want any semblance of reliability.
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.
If your program generates signals, it could generate a flood unintentionally. If you put a print statement in the handler, you can get this result.
It's not terribly surprising, but good to know.
The C printf function is not async signal safe and is one of the examples in the manual: https://man7.org/linux/man-pages/man7/signal-safety.7.html