Signal handlers call non-signal-safe functions #103
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
KittenSquad/glewlwyd#103
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Describe the issue
The
exit_handlertries to write log messages withy_log_message, which is not async signal safe. This can lead to a deadlock.I got this stack trace:
As can be seen, the thread is busy inside
malloc()in frame 9. It likely has a lock on some internal malloc structure. In this state a signal arrives and the thread jumps to a signal handler (frame 7). This handler (indirectly) callsmalloc()again (frame 3) which tries to obtain a lock. Most likely this is the same lock that is already held in frame 9, causing a deadlock.Expected behavior
A deadlock is annoying because it is not clear from the outside that the program stopped working, so that should be avoided. For non-fatal signals like SIGINT the main function could print the message after it got woken up by the condition variable, but for signals like SIGSEGV that's not an option, so I don't see how a message could be logged there without running into these kind of problems.
Plain
write()is async signal safe, which could be used to write a message to stderr, but I'm not sure if doing that is worth it.System:
Latest
babelouest/glewlwyddocker.Thanks for reporting,
I've never experienced deadlocks in Glewlwyd before, but I don't use docker images to run it so that may be because of Alpine Linux's different implementation.
I've created an issue in yder to discuss yder's side: babelouest/yder#16 , so you can add your feedback on it.
Concerning this issue, if I use less
o_mallocin yder and more mutexes, this could ba a start, don't you think?The root cause here was corruption of the internal malloc structures, I assume from a double free() caused by #104. This corruption resulted in a segfault in a later malloc call, leading to the above situation.
What exactly happens depends on the implementation of malloc. Alpine uses musl libc instead of the more common glibc, so it has a different malloc() implementation an behaves differently on such problems.
Generally I don't think more mutexes will help (rather the opposite). The main problem causing the deadlock is if a thread holds a lock, then gets interrupted by a signal, and then tries to aquire the same lock again in the signal handler. On the other hand if there is a static buffer not protected by a mutex that gets written to multiple times that will also not work out well...
Sadly I'm not an expert for signal handling, but maybe a strategy would be to have a dedicated thread to do signal handling, while signal handling is blocked for the other threads that do the real work. The signal handling thread would not do any malloc/locking/... and therefore if a signal arrives that thread would never get interrupted inside a lock. The signal handler would then be free to use the normal logging functions without danger.
There would still be some complications. Some signals might be delivered to specific threads, like SIGSEGV, and maybe always have their handler called on that thread (see some discussion here). That would need further investigation, I'm not sure about the details. Also for SIGSEGV and similar things you can never be sure calling a logging function will actually work. In the SIGSEGV case you most likely have some memory corruption somewhere and basically whatever you do might just run into another segmentation fault.
Indeed, if I add a static buffer, I need to add a mutex, otherwise hell will be unleashed upon multi-threaded applications...
Could you provide a pull request for that? Also, would it be wise to do it, if the original problem was solved using
getaddrinfo()?I'll have a look and see if I come up with something useful.
@sth , HYI, I reverted yder's last changes that were added to make it thread-safe, because as far as I can see, there's no visible difference in the performance or the safeness.
On the other hand, I made tests using
yder/gethostbynameandyder/getaddrinfoin a multi-thread environment on a Linux Alpine docker instance.The result shows that the new code doesn't change if the test program is more safe or not. The only visible change is when you switch to
getaddrinfo, then the test program doesn't fails.I came to the conclusion that adding static buffers and mutexes in yder is at least not useful, at worst could lead to new kind of problems. Therefore I reverted the changes.
Here is the surce program I used for my tests
Should be fixed now that #105 is merged, thanks @sth !