I'm really uncertain about the requirements POSIX places on the safety of fork
in the presence of threads and signals. fork
is listed as one of the async-signal-safe functions, but if there is a possibility that library code has registered pthread_atfork
handlers which are not async-signal-safe, does this negate the safety of fork
? Does the answer depend on whether the thread in which the signal handler is running could be in the middle of using a resource that the atfork handlers need? Or said a different way, if the atfork handlers make use of synchronization resources (mutexes, etc.) but fork
is being called from a signal handler which executed in a thread that never accesses these resources, is the program conforming?
Building on this question, if "thread-safe" forking is implemented internally in the system library using the idioms suggested by pthread_atfork
(obtain all locks in the prefork handler and release all locks in both the parent and child postfork handlers), then is fork
ever safe to use from signal handlers in a threaded program? Isn't it possible that the thread handling the signal could be in the middle of a call to malloc
or fopen
/fclose
and holding a global lock, resulting in deadlock during fork
?
Finally, even if fork
is safe in signal handlers, is it safe to fork
in a signal handler and then return from the signal handler, or does a call to fork
in a signal handler always necessitate a subsequent call to _exit
or one of the exec
family of functions before the signal handler returns?
a child process inherits signal settings from its parent during fork (). When process performs exec (), previously ignored signals remain ignored but installed handlers are set back to the default handler.
a fork is listed as Async-signal safe, so it can be used.
An async-signal-safe function is one that can be safely called from within a signal handler. Many functions are not async- signal-safe. In particular, nonreentrant functions are generally unsafe to call from a signal handler.
signal is a per process call, not a per thread one, if you call it it sets the handler for all threads in the process.
Trying my best to answer all the sub-questions; I apologise that some of this is vaguer than it ideally should be:
If there is a possibility that library code has registered pthread_atfork handlers which are not async-signal-safe, does this negate the safety of fork?
Yes. The fork documentation explicitly mentions this:
When the application calls fork() from a signal handler and any of the
fork handlers registered by pthread_atfork() calls a function that is
not asynch-signal-safe, the behavior is undefined.
Of course, this means you can't actually use pthread_atfork()
for its intended purpose of making multi-threaded libraries transparent to processes that believe they are single-threaded, because none of the pthread synchronisation functions are async-signal-safe; this is noted as a defect in the spec, see http://www.opengroup.org/austin/aardvark/latest/xshbug3.txt (search for "L16723").
Does the answer depend on whether the thread in which the signal handler is running could be in the middle of using a resource that the atfork handlers need? Or said a different way, if the atfork handlers make use of synchronization resources (mutexes, etc.) but fork is being called from a signal handler which executed in a thread that never accesses these resources, is the program conforming?
Strictly speaking the answer is no, because the according to the spec, functions are either async-signal-safe or they're not; there's no concept of "safe under certain circumstances". In practice you might well get away with it, but you would be vulnerable to a clunky-but-correct implementation that didn't partition its resources in the way you were expecting.
Building on this question, if "thread-safe" forking is implemented internally in the system library using the idioms suggested by pthread_atfork (obtain all locks in the prefork handler and release all locks in both the parent and child postfork handlers), then is fork ever safe to use from signal handlers in a threaded program? Isn't it possible that the thread handling the signal could be in the middle of a call to malloc or fopen/fclose and holding a global lock, resulting in deadlock during fork?
If it were implemented in that way, then you're right, fork()
from a signal handler would never be safe, because attempting to obtain a lock might deadlock if the calling thread already held it. But this implies that an implementation using such a method would not be conforming.
Looking at glibc as one example, it doesn't do that - rather, it takes two approaches: firstly, the locks that it does obtain are recursive (so if the current thread already has them, their lock count will simply be increased); further, in the child process, it simply unilaterally overwrites all the locks - see this extract from nptl/sysdeps/unix/sysv/linux/fork.c
:
/* Reset the file list. These are recursive mutexes. */
fresetlockfiles ();
/* Reset locks in the I/O code. */
_IO_list_resetlock ();
/* Reset the lock the dynamic loader uses to protect its data. */
__rtld_lock_initialize (GL(dl_load_lock));
where each of the resetlock
and lock_initialize
functions ultimately call glibc's internal equivalent of pthread_mutex_init()
, effectively resetting the mutex regardless of any waiters.
I think the theory is that, by obtaining the (recursive) lock it's guaranteed that no other threads will be touching the data structures (at least in a way that might cause a crash), and then resetting the individual locks ensures the resources aren't permanently blocked. (Resetting the current thread's lock is safe since there are now no other threads to contend for the data structure, and indeed won't be until whatever function is using the lock has returned).
I'm not 100% convinced that this covers all eventualities (not least because if/when the signal handler returns, the function that's just had its lock stolen will try to unlock it, and the internal recursive unlock function doesn't protect against unlocking too many times!) - but it seems that a workable scheme could be built on top of async-signal-safe recursive locks.
Finally, even if fork is safe in signal handlers, is it safe to fork in a signal handler and then return from the signal handler, or does a call to fork in a signal handler always necessitate a subsequent call to _exit or one of the exec family of functions before the signal handler returns?
I assume you're talking about the child process? (If fork()
being async-signal-safe means anything then it should be possible to return in the parent!)
Not having found anything in the spec that states otherwise (though I may have missed it) I believe it should be safe - at least, 'safe' in the sense that returning from the signal handler in the child doesn't imply undefined behaviour in and of itself, though the fact that a multi-threaded process has just forked may imply that an exec*()
or _exit()
is probably the safest course of action.
I'm adding this answer because it looks like fork()
is likely no longer considered async-safe. At least this seems to be the case with glibc
but perhaps support no longer exists in POSIX. The answer currently marked as "accepted" seems to come to the conclusion that it is safe, but at least in glibc
that's likely not the case.
Building on this question, if "thread-safe" forking is implemented internally in the system library using the idioms suggested by pthread_atfork (obtain all locks in the prefork handler and release all locks in both the parent and child postfork handlers), then is fork ever safe to use from signal handlers in a threaded program? Isn't it possible that the thread handling the signal could be in the middle of a call to malloc or fopen/fclose and holding a global lock, resulting in deadlock during fork?
Indeed! It looks as if The Open Group resolved to remove it from the list for this very reason.
IEEE 1003.1c-1995 Interpretation Request #37 regards pthread_atfork
.
The Interpretation Committee believes that ... the following explanatory additions be made: Pg 78 line 864 "Additionally, invocations of the fork handlers set by pthread_atfork from a fork called from a signal handler are required to be async safe."
glibc
Bug 4737 identifies a resolution that fork()
be evicted from the async-safe functions list and posix_spawn()
be used to fill its place. Unfortunately, it was resolved as WONTFIX
so not even the manpages were updated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With