4 ms·
> As long as the called functions are fully async-signal-safe/reentrant. That is not accurate. If I have a single-threaded program sitting around a ppoll(2) lo
by quotemstr 3y ago
> As long as the called functions are fully async-signal-safe/reentrant.
That is not accurate. If I have a single-threaded program sitting around a ppoll(2) loop and a signal can arrive only inside my main loop ppoll(), then I know a priori that my signal handler can't be interrupting non-reentrant code and so I can call whatever I want inside it.
> It used to be even more sensitive, in that not all register state was correctly saved/restored on Linux.
Don't confuse architectural flaws with implementation bugs. I'm reminded of an argument on emacs-devel in which someone argued that we couldn't call malloc(3) in a multi-threaded program because one beta version of glibc once had a thread safety bug in the malloc implementation.
> SIGBUS is a POSIX-way to deal with mmap truncation, but sealed memfds can be used to avoid the issue altogether.
Sealing a file descriptor doesn't physically seal a USB key into a USB port. :-) You also get SIGBUS on IO failures on mmaped files, and surprise removal is an IO failure. There's really no alternative to a synchronous signal here --- a regular file being mmap()ed isn't a userfaultfd.
IMHO, I think it's crying shame that Hotspot (last time I checked --- maybe it's fixed?) doesn't watch for SIGBUS on access to MappedByteBuffer and translate surprise USB device file removal into a nice clear VM-level exception.
Even for cases for which userfaultfd can work, a synchronous signal can be more efficient because it involves just one entry into the kernel. (sigreturn is optional.) I'd really hate to give up the conventional signal mechanism entirely, although of course I approve of things like signalfd and userfault that reduce the need for signal handling.
- arghwhat 3y ago> That is not accurate. If I have a single-threaded program sitting around a ppoll(2) loop and a signal can arrive only inside my main loop ppoll(), then I know a priori that my signal handler can't be interrupting non-reentrant code and so I can call whatever I want inside it. If you have a single-threaded program sitting in ppoll, you cannot ever receive a SIGSEGV during said ppoll unless you passed it bugus fds, timeout or sigmask pointers. A sleeping process cannot segfault. If you register a SIGSEGV handler, you have zero guarantees that it will only fire at a particular time in your code as it is delivered on any pagefault from any code accidentally generating it. This is why the async-signal-safe rules apply. If you try handling such fault, what needs to be reentrant is the full call stack that lead to the fault, and every call made from the handler. If, for example, the fault is generated from an event loop handler (in case of a single-threaded example, most things run off event loop handlers), the signal handler must in turn not touch the event loop (no adding/removing/adjusting events, no dispatch) unless the event loop is fully reentrant.
- quotemstr 3y ago> you cannot ever receive a SIGSEGV during said ppoll Unless someone sends one with kill(2). Also, I was talking about signals in general, not SIGSEGV in particular. Who uses SIGSEGV as an async work dispatch mechanism? > unless you passed it bugus fds, timeout or sigmask pointers. A sleeping process cannot segfault. No, you get EFAULT. System calls don't work that way. > If you register a SIGSEGV handler, you have zero guarantees that it will only fire at a particular time in your code as it is delivered on any pagefault from any code accidentally generating it That's why you use sigaction(2) to register signal handlers --- your callback gets both a siginfo_t and a ucontext_t you can use to figure out whether your segfault came from a region of code you know about or some other random thing going wrong in your process. In principle, you can do the non-reentrant thing after having checked that the signal came from the context you expect, and you can do this checking in an async-signal-safe manner. > If, for example, the fault is generated from an event loop handler (in case of a single-threaded example, most things run off event loop handlers), the signal handler must in turn not touch the event loop (no adding/removing/adjusting events, no dispatch) unless the event loop is fully reentrant. Of course. That's a matter of program design.
- arghwhat 3y ago> No, you get EFAULT. System calls don't work that way. `ppoll(2)` is a C library function you call. On glibc, depending on which implementation you hit on your architecture, the `ppoll` call will segfault before the syscall if you call it with bogus timeout or sigmask pointers, as glibc is dereferencing them. > That's why you use sigaction(2) to register signal handlers --- your callback gets both a siginfo_t and a ucontext_t you can use to figure out whether your segfault came from a region of code you know about or some other random thing going wrong in your process. It is true that you could try to figure out whether the faulting address is within a range you thought belonged to a particular thing, but I don't see any reason or benefit. However, this is fragile: Distinguishing between a page fault from a corrupt program state and a page fault in a valid program depends on the program state (specifically, inspecting memory that is likely foo), which in turn makes the output undefined. You won't get false negatives, but will get false positives. You could also try to establish a list of all program counter values (PIE/PIC caveats), and check if the signal originated from near those, but... Ugh. Now, any other signal I could level with you, but not SIGSEGV. Not until I see some code where it's truly justified: a real, tangible benefit that can only be obtained in this fashion, justifying all the gymnastics and (in my opinion) nastiness of trying to handle a signal that should never be handled in the first place (not even for crash dumps, they're always worse than a proper core).