5 ms·
The perils of pause(2) (2023)
- deleted 3y ago[deleted]
- userbinator 3y agoThis seems like another instance of the same general race condition often referred to as the lost/missed wakeup problem.
- unilynx 3y agoA much cleaner and portable solution to the races triggered by duplicated and early signals is to set up a pipe and have signal handlers write the signal number to that pipe. A write syscall is permitted in a signal handler and now you can (p)select/poll the signals like any other file descriptor
- jwilk 3y agoIt's mentioned in the article: > There's also the classic self-pipe trick.
- iforgotpassword 3y agoAnd ideally, have a thread for that that you spawn in the beginning, then block signals on the main thread before spawning any other thread or initializing any library that might potentially do so.
- Joker_vD 3y agoI'll take "Things that the OS should probably be doing itself but instead we end up re-doing it in the userspace, poorly, for historical reasons" for 400, Alex.
- pwdisswordfishc 3y agoIt does. https://www.man7.org/linux/man-pages/man2/signalfd.2.html https://www.man7.org/linux/man-pages/man2/signalfd.2.html
- marcosdumay 3y agoWhy people still didn't deprecate signal handlers (except for 15)?
- iforgotpassword 3y agoObligatory https://ldpreload.com/blog/signalfd-is-useless?reposted-on-request https://ldpreload.com/blog/signalfd-is-useless?reposted-on-r... post. :) It's a divisive article, but I generally agree, saving you a thread is basically what it mostly does, and there's already enough boilerplate out there to do that. Plus you sacrifice portability.
- Joker_vD 3y agoOne of the root problems with the UNIX signals IMHO is that they conflate 3 different useful concepts together. The first concept is that of asynchronous external events that the process would like to react to in a timely manner (e.g. SIGINT): those can be processed on a dedicated thread, maybe it could be created anew every time a signal is delivered a la Windows [0], maybe it could be created once at the moment of execve and ELF format should get a "secondary entry point" field for it, whatever. The second concept is that of synchronous internal events that the process has caused itself (e.g. SIGSEGV, SIGILL): well, those are just low-level exceptions, and could be processed on the thread that caused them. Some sort of structural exception handling [1] (only less awkward) could work, I think? An interesting mix of those two kinds of signals is SIGALRM, which is an asynchronous internal event. Can be quite useful for implementing lighter-weighted/cooperative multitasking, I guess: it basically makes the OS to regularly punt your instruction pointer somewhere else where you could e.g. flush some memory and raise semaphores so a sibling thread could notice and report the progress to the user, while the main thread returns back to number crunching, or you could use it to to force coroutines/fibers to yield or something. To be fair, not sure what the proper interface for this should look like. And the third concept is that of the control operations acting on the process itself, the operations that the process gets no chance to react to: SIGKILL, SIGSTOP, SIGCONT. They are not really process signals, they are the API of the process manager/scheduler that were implemented with the signals machinery because what was the other choice? Introducing new system calls? [0] https://learn.microsoft.com/en-us/windows/console/handlerroutine https://learn.microsoft.com/en-us/windows/console/handlerrou... [1] https://learn.microsoft.com/en-us/windows/win32/debug/structured-exception-handling https://learn.microsoft.com/en-us/windows/win32/debug/struct...
- deleted 3y ago[deleted]
- zokier 3y agoIs there any case where anything remotely related to signals is not full of perils? UNIX has plenty of rough edges, but signals must be one of the worst..
- deleted 3y ago[deleted]
- TeMPOraL 3y agoIs there even a way to correctly deal with signals in the first place? The more posts like this I read, the more I start to feel that there is no 100% correct way of handling signals, you can at best minimize the probability of getting hit by one at a wrong moment. Which is OK in real life, but feels bad for a fundamental part of an OS.
- CodesInChaos 3y agoI think the right choice depends on the kind of signal: 1. SigPipe and similar synchronous signals can be disabled. You already get an error code from the IO operation anyways, so they're redundant. It's kind of useful for traditional unix command line tools, since it'll just terminate the process when the output pipe gets closed. 2. SigInt and similar messages from other processes, should be handled via signalfd or the self-pipe trick. That way they look like any other asynchronous message that might come in via a pipe or socket. 3. SigSegV and other severe errors can just terminate the process Windows chose a different approach: SigSegV is a (structured) exception which causes unwinding. Unwinding has its own set of challenges, especially as language boundaries are crossed. SigInt calls a registered callback on a separate thread, so the handler is not subjected in the same limitations as a signal of an existing thread, and can use the same synchronization primitives as any other multi threaded code. GUI applications have a message queue per window, which is used for similar purposes (sometimes it makes sense for a console application to create a hidden window to receive such messages).
- pm215 3y agoThere definitely are correct patterns for handling signals -- the problem is more that there are also quite a lot of "looks plausible but has a race condition" ways to write code. The whole signals infrastructure in Unix has always been a bit dubious IMHO -- my impression from reading the Sixth Edition sources in the Lions book was that it looked like it had basically been put in to handle "signals are fundamentally fatal" cases like ctrl-C of a process from the terminal or SIGSEGV and friends, with the signal handler really only being there to do cleanup before the process exited. If that's all signals are doing then none of the race condition stuff matters (including the traditional "when a signal handler is called it resets to SIG_DFL" race that was fixed by BSD-style signals) and nor do you need to care about the enormous mountain of pain that is having to handle EINTR return values from system calls. But once the mechanism was there it got used for other things, many of which involved "the process receives and deals with signals as part of its normal continuing execution".
- ajross 3y agoThis isn't really a signals problem. This is a fundamentally racy API. Any decision of the form "go to sleep until this condition is true" can't be done reliably unless the code that decides "this condition" is atomic with respect to the entrance to the sleep state. That is, if the condition might change after you've decided it's false but before you suspend, you might never wake up. There are lots of solutions to this. Synchronizing along a "queue" a-la file descriptors or semaphores is the most common. The generalization of this "suspend until" problem is captured by the "condition variable" abstraction, which combines the sleep with a lock that provides mutual exclusion and can be released atomically on suspend. But the pause() system call didn't understand any of that, so it's unresolvably buggy without some extra layer somewhere to act as a backstop and wake up incorrectly-sleeping processes.
- Arch-TK 3y agopause() is fine, as long as you don't do it conditionally.
- ajross 3y agoWhat's a "non-conditional" pause though? Doing it in a main loop is still adjacent to a "while(something_to_do)" which isn't synchronized. I mean, yes, there are circumstances where you don't need a full condition variable. But those tend to be fragile and not subject to "just follow this one rule" design principles, and so should be avoided. Broadly I stand by the point: pause() as designed was a mistake and you shouldn't touch it. If you want signals to integrate into your main loop use signalfd() instead.
- Arch-TK 3y agoIf everything you wish to achieve in response to a signal can be achieved within the signal handler, you do not need a condition. Just `while (1) pause();`. Granted, this isn't a very common situation, but I can imagine a few use-cases.
- deleted 3y ago[deleted]
- majke 3y agosigprocmask is a neat solution, but requires a syscall. Would it be theoretically possible to share the signal mask between the userspace and the kernel in no-cost way? I'm thinking vDSO style, mmaped var. This could have big implications, for example most classic coroutine implementations using setjmp do some procmask dance.