On Fri, Jan 09, 2009 at 09:39:08AM -0800, Julian Elischer wrote:
> Brian Fundakowski Feldman wrote:
>> On Thu, Jan 08, 2009 at 11:09:16PM -0800, Julian Elischer wrote:
>>> Brian Fundakowski Feldman wrote:
>>>> On Thu, Jan 08, 2009 at 10:44:20PM -0500, Daniel Eischen wrote:
>>>>> On Thu, 8 Jan 2009, Brian Fundakowski Feldman wrote:
>>>>> 
>>>>>> It appears that the post-fork hooks for malloc(3) are somewhat broken 
>>>>>> such that
>>>>>> when a threaded program forks, and then its child attempts to go 
>>>>>> threaded, it
>>>>>> deadlocks because it already appears to have locks held.  I am not 
>>>>>> familiar
>>>>>> enough with the current libthr/libc/rtld-elf interaction that I've been 
>>>>>> able
>>>>>> to fix it myself, unfortunately.
>>>>> There's really nothing to fix - according to POSIX you are only
>>>>> allowed to call async-signal-safe functions in the child forked
>>>>> from a threaded process.  If you are trying to do anything other
>>>>> than that, it may or may not work on FreeBSD, but it is not
>>>>> guaranteed and is not portable.
>>>>> 
>>>>> The rationale is that what is the point of forking and creating
>>>>> more threads, when you can just as easily create more threads in
>>>>> the parent without forking?  The only reason to fork from a threaded
>>>>> process is to call one of the exec() functions.
>>>> Well, it worked until the last major set of changes to malloc.  For me, 
>>>> the point
>>>> was that I was able to have transparent background worker threads in any 
>>>> program
>>>> regardless of its architecture, using the standard pthread fork hooks.  
>>>> Could you
>>>> point me to the POSIX section covering fork and threads?  If it's really 
>>>> not
>>>> supposed to work then that's fine, but there's an awful lot of code there 
>>>> dedicated
>>>> to support going threaded again after a fork.
>>>> 
>>> Practically, you can't go threaded again after a fork
>>> (by which I mean creating new threads or use things
>>> like mutexes etc.) in any posix system I know of.
>>> 
>>> It would require that:
>>>  The forking thread stop until:
>>>   Every other thread has released every resource it owns
>>>   and reports itself to be in a "safe quiescent state",
>>>   or at least report every resource it owns, especially
>>>   locks,
>>>  and
>>>  After the fork:
>>>   The child, post fork, to take ownership of all
>>>   of them, and free them.
>>> 
>>> You might be able to do that in a simple
>>> threaded program, but consider then that the libraries may have
>>> threads running in them of which you are unaware, and that
>>> some of the resources may interract with resources owned by the
>>> forking thread.
>>> 
>>> Add to this that there may be a signal thrown into this mix as well
>>> 
>>> (signals are the bane of thread developement)
>> 
>> Well, I wouldn't mind showing all of you what I can of what I had been doing
>> with the background threads -- it works pretty well modulo this particular
>> malloc lock bug.  Due to it being inappropriate to share library resources
>> between a child and parent for an open socket connection, I always considered
>> the only "safe" behavior to be going single-threaded for the duration of the 
>> fork
>> processes in both the parent and child, and the pthread_atfork(3) hooks have 
>> been
>> sufficient to do so.
> 
> 
> ahhhh
> well going single threaded for the duration of the fork, changes 
> everything!

Could you, and anyone else who would care to, check this out?  It's a regression
fix but it also makes the code a little bit clearer.  Thanks!

Index: lib/libc/stdlib/malloc.c
===================================================================
--- lib/libc/stdlib/malloc.c    (revision 187160)
+++ lib/libc/stdlib/malloc.c    (working copy)
@@ -415,6 +415,7 @@
 
 /* Set to true once the allocator has been initialized. */
 static bool malloc_initialized = false;
+static bool malloc_during_fork = false;
 
 /* Used to avoid initialization races. */
 static malloc_mutex_t init_lock = {_SPINLOCK_INITIALIZER};
@@ -1205,7 +1206,7 @@
 malloc_mutex_lock(malloc_mutex_t *mutex)
 {
 
-       if (__isthreaded)
+       if (__isthreaded || malloc_during_fork)
                _SPINLOCK(&mutex->lock);
 }
 
@@ -1213,7 +1214,7 @@
 malloc_mutex_unlock(malloc_mutex_t *mutex)
 {
 
-       if (__isthreaded)
+       if (__isthreaded || malloc_during_fork)
                _SPINUNLOCK(&mutex->lock);
 }
 
@@ -1260,7 +1261,7 @@
 {
        unsigned ret = 0;
 
-       if (__isthreaded) {
+       if (__isthreaded || malloc_during_fork) {
                if (_pthread_mutex_trylock(lock) != 0) {
                        /* Exponentially back off if there are multiple CPUs. */
                        if (ncpus > 1) {
@@ -1296,7 +1297,7 @@
 malloc_spin_unlock(pthread_mutex_t *lock)
 {
 
-       if (__isthreaded)
+       if (__isthreaded || malloc_during_fork)
                _pthread_mutex_unlock(lock);
 }
 
@@ -5515,9 +5516,8 @@
 void
 _malloc_prefork(void)
 {
+       arena_t *larenas[narenas];
        bool again;
-       unsigned i, j;
-       arena_t *larenas[narenas], *tarenas[narenas];
 
        /* Acquire all mutexes in a safe order. */
 
@@ -5530,19 +5530,23 @@
         */
        memset(larenas, 0, sizeof(arena_t *) * narenas);
        do {
+               unsigned int i;
+
                again = false;
 
                malloc_spin_lock(&arenas_lock);
                for (i = 0; i < narenas; i++) {
                        if (arenas[i] != larenas[i]) {
+                               arena_t *tarenas[narenas];
+                               unsigned int j;
+
                                memcpy(tarenas, arenas, sizeof(arena_t *) *
                                    narenas);
                                malloc_spin_unlock(&arenas_lock);
                                for (j = 0; j < narenas; j++) {
                                        if (larenas[j] != tarenas[j]) {
                                                larenas[j] = tarenas[j];
-                                               malloc_spin_lock(
-                                                   &larenas[j]->lock);
+                                               
malloc_spin_lock(&larenas[j]->lock);
                                        }
                                }
                                again = true;
@@ -5558,6 +5562,7 @@
 #ifdef MALLOC_DSS
        malloc_mutex_lock(&dss_mtx);
 #endif
+       malloc_during_fork = true;
 }
 
 void
@@ -5582,6 +5587,12 @@
                if (larenas[i] != NULL)
                        malloc_spin_unlock(&larenas[i]->lock);
        }
+       /*
+        * This ends the special post-__isthreaded exemption behavior for
+        * malloc stuff.  We should really be single threaded right now
+        * in effect regardless of __isthreaded status.
+        */
+       malloc_during_fork = false;
 }
 
 /*
Index: lib/libthr/thread/thr_fork.c
===================================================================
--- lib/libthr/thread/thr_fork.c        (revision 187160)
+++ lib/libthr/thread/thr_fork.c        (working copy)
@@ -105,7 +105,7 @@
        struct pthread_atfork *af;
        pid_t ret;
        int errsave;
-       int unlock_malloc;
+       int was_threaded;
        int rtld_locks[MAX_RTLD_LOCKS];
 
        if (!_thr_is_inited())
@@ -122,16 +122,16 @@
        }
 
        /*
-        * Try our best to protect memory from being corrupted in
-        * child process because another thread in malloc code will
-        * simply be kill by fork().
+        * All bets are off as to what should happen soon if the parent
+        * process was not so kindly as to set up pthread fork hooks to
+        * relinquish all running threads.
         */
        if (_thr_isthreaded() != 0) {
-               unlock_malloc = 1;
+               was_threaded = 1;
                _malloc_prefork();
                _rtld_atfork_pre(rtld_locks);
        } else {
-               unlock_malloc = 0;
+               was_threaded = 0;
        }
 
        /*
@@ -159,7 +159,7 @@
                _thr_umutex_init(&curthread->lock);
                _thr_umutex_init(&_thr_atfork_lock);
 
-               if (unlock_malloc)
+               if (was_threaded)
                        _rtld_atfork_post(rtld_locks);
                _thr_setthreaded(0);
 
@@ -173,7 +173,7 @@
                /* Ready to continue, unblock signals. */ 
                _thr_signal_unblock(curthread);
 
-               if (unlock_malloc)
+               if (was_threaded)
                        _malloc_postfork();
 
                /* Run down atfork child handlers. */
@@ -188,7 +188,7 @@
                /* Ready to continue, unblock signals. */ 
                _thr_signal_unblock(curthread);
 
-               if (unlock_malloc) {
+               if (was_threaded) {
                        _rtld_atfork_post(rtld_locks);
                        _malloc_postfork();
                }
Index: tools/regression/pthread/malloc_thread_fork_survival/no_deadlock.c
===================================================================
--- tools/regression/pthread/malloc_thread_fork_survival/no_deadlock.c  
(revision 0)
+++ tools/regression/pthread/malloc_thread_fork_survival/no_deadlock.c  
(revision 0)
@@ -0,0 +1,55 @@
+/*
+ * Public domain, originally by Brian Fundakowski Feldman <gr...@freebsd.org>.
+ * $FreeBSD$
+ */
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+void *
+noop(void *unused)
+{
+       return (NULL);
+}
+
+void *
+exit0(void *unused)
+{
+       const char ok[] = "ok\n";
+       write(1, ok, sizeof(ok));
+       exit(0);
+}
+
+void *
+exitN(int code)
+{
+       if (code == 0) {
+               exit0(NULL);
+       } else {
+               const char not_ok[] = "not ok\n";
+               write(1, not_ok, sizeof(not_ok));
+               exit(code);
+       }
+}
+
+int
+main()
+{
+       pthread_t thread;
+       int exited;
+
+       pthread_create(&thread, NULL, noop, NULL);
+       pthread_join(thread, NULL);
+       if (fork() == 0) {
+               alarm(1);
+               pthread_create(&thread, NULL, exit0, NULL);
+               pthread_join(thread, NULL);
+               /* UNREACHED */
+       }
+       wait(&exited);
+       exitN(WIFSIGNALED(exited) ? WTERMSIG(exited) : WEXITSTATUS(exited));
+}
Index: tools/regression/pthread/malloc_thread_fork_survival/Makefile
===================================================================
--- tools/regression/pthread/malloc_thread_fork_survival/Makefile       
(revision 0)
+++ tools/regression/pthread/malloc_thread_fork_survival/Makefile       
(working copy)
@@ -1,8 +1,8 @@
 # $FreeBSD$
 
-PROG=  cv_cancel1
+PROG=  no_deadlock
 NO_MAN=
 
-LDADD= -lpthread
+LDADD= -lthr
 
 .include <bsd.prog.mk>

-- 
Brian Fundakowski Feldman                           \'[ FreeBSD ]''''''''''\
  <> gr...@freebsd.org                               \  The Power to Serve! \
 Opinions expressed are my own.                       \,,,,,,,,,,,,,,,,,,,,,,\
_______________________________________________
freebsd-hackers@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-hackers
To unsubscribe, send any mail to "freebsd-hackers-unsubscr...@freebsd.org"

Reply via email to