Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

On linux, the behavior for locking a mutex (I tested a GNU C++11 std::mutex) from a signal handler is to consider it (at least for the interrupted thread) to have been unlocked. This allows intuitive synchronization from handlers and avoids deadlocks, which I'm assuming is facilitated in the kernel-futex design. If any kernel hackers want to chime in on why this works (and is safe (is safe?)) in the face of most unix specifications, generic docs and articles like this it may be enlightening.


You are well into UB land. The behaviour you describe is very dangerous as the signal handler will be accessing the mutex protected data structure while it is in a potentially inconsistent state.

The right, portable way to signal from a signal handler are POSIX semaphores that on glibc are a thin wrapper over futexes. Any data structure access must be non blocking.

Edit: autocorrect


I should mention I was using x86. My initial assumption was that the kernel simply references the robust list (even if it was initially entirely resolved in userspace), and yields back to the interrupted thread -- I should emphasize my test showed the kernel breaks into the lock and presents a semi-coherent as-is structure entering and exiting the handler's lock. Of course this is all way way UB for portability or complex structures indeed...


Even on x86, you could have a pthread_mutex protecting a struct with two integers that need to be updated "atomically", and have a signal delivered in the middle?


Sure, and then the handler only sees one integer as updated and the other integer will be updated after the handler. The lock gets silently broken unfortunately, but there's probably a useful reason for why this is. It could just deadlock instead.


Deadlocking would be the ideal outcome. Deadlocks are easy to debug. Silent concurrent memory corruptions not so much.


Well that's just plain broken.


If the signal handler breaks the lock, I suspect it's more by accident than design. This is not a reasonable thing to depend on.

> presents a semi-coherent as-is structure entering and exiting the handler's lock

I don't understand the word "semi-coherent" in this sentence. Effectively, a signal handler interrupts some thread and doesn't allow it to proceed until the signal handler is finished.

In general, you could say there are three aspects to why locking is needed:

* hardware memory barriers. Here I think you're fine; really the signal handler runs on the thread in question so anything the interrupted thread did the signal handler sees on entry and likewise anything the signal handler did the interrupted thread sees on exit.

* simplifying your code (or the library code you're calling). Even ignoring CPU/compiler re-ordering, it's much saner to just write your program such that you guarantee certain invariants are held when the locks are not held and make no such guarantees while the locks are held. If you have to look at all possible interactions line-by-line or instruction-by-instruction, there are so many more combinations to test. It's not as hopeless for thread-vs-signal as with thread-vs-thread (O(instructions in critical section) vs O(2^instructions) orderings, as the signal handler always runs to completion instead of interleaving with its thread arbitrarily) but it's still plenty bad enough.

* compiler memory barriers. You're basically screwed here IIUC. Compilers can and do bizarre things such as reusing your variable to store something completely different. https://software.intel.com/en-us/blogs/2013/01/06/benign-dat...

Except as a last-ditch attempt at gathering debugging information on crash (where not perfectly reliable is likely acceptable), I'd say it's totally unreasonable to ever access mutex-protected state from a signal handler that can run while the thread it interrupts might hold the lock. Note this includes malloc and free. Don't call those from a signal handler. (I suppose you might get away with it if the interrupted thread never calls them or only does so with the signal handler blocked, but that'd be very unusual, and it's still not guaranteed to be safe according to the POSIX standard. I'm not sure off-hand what the kernel/libc might do that would mess this up but I wouldn't bet on it.)

As I mentioned in another comment, I think of signals as two separate things. Process-directed signals can be handled relatively simple without even requiring signal handlers. Thread-directed signals are tricky and you're doing them wrong.


Do you have a source for this? Are you talking about pthread_mutex_lock() ? Maybe I'm misreading you, but it sounds like you are saying it would be safe to treat a mutex as unconditionally unlocked inside a signal handler, which doesn't make much sense (what if a signal is delivered between _lock() and _unlock()? )




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: