[Python-Dev] Eureka! (Re: test_fork fails --with-thread) (original) (raw)
Charles G Waldman cgw@fnal.gov
Fri, 18 Aug 2000 23:31:06 -0500 (CDT)
- Previous message: [Python-Dev] RE: [Patches] [Patch #101055] Cookie.py
- Next message: [Python-Dev] Re: Eureka! (Re: test_fork fails --with-thread)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
Last month there was a flurry of discussion, around
http://www.python.org/pipermail/python-dev/2000-July/014208.html
about problems arising when combining threading and forking. I've been reading through the python-dev archives and as far as I can tell this problem has not yet been resolved.
Well, I think I understand what's going on and I have a patch that fixes the problem.
Contrary to some folklore, you can use fork() in threaded code; you just have to be a bit careful about locks...
Rather than write up a long-winded explanation myself, allow me to quote:
from "man pthread_atfork":
... recall that fork(2) duplicates the whole memory space,
including mutexes in their current locking state, but only the
calling thread: other threads are not running in the child
process. Thus, if a mutex is locked by a thread other than
the thread calling fork, that mutex will remain locked
forever in the child process, possibly blocking the execu-
tion of the child process.
and from http://www.lambdacs.com/newsgroup/FAQ.html#Q120
Q120: Calling fork() from a thread
Can I fork from within a thread ?
Absolutely.
If that is not explicitly forbidden, then what happens to the other threads in the child process ?
There ARE no other threads in the child process. Just the one that forked. If your application/library has background threads that need to exist in a forked child, then you should set up an "atfork" child handler (by calling pthread_atfork) to recreate them. And if you use mutexes, and want your application/library to be "fork safe" at all, you also need to supply an atfork handler set to pre-lock all your mutexes in the parent, then release them in the parent and child handlers. Otherwise, ANOTHER thread might have a mutex locked when one thread forks -- and because the owning thread doesn't exist in the child, the mutex could never be released. (And, worse, whatever data is protected by the mutex is in an unknown and inconsistent state.)
Below is a patch (I will also post this to SourceForge)
Notes on the patch:
I didn't make use of pthread_atfork, because I don't know how portable it is. So, if somebody uses "fork" in a C extension there will still be trouble.
I'm deliberately not cleaning up the old lock before creating the new one, because the lock destructors also do error-checking. It might be better to add a PyThread_reset_lock function to all the thread_*.h files, but I'm hesitant to do this because of the amount of testing required.
Patch:
Index: Modules/signalmodule.c
RCS file: /cvsroot/python/python/dist/src/Modules/signalmodule.c,v retrieving revision 2.53 diff -c -r2.53 signalmodule.c *** Modules/signalmodule.c 2000/08/03 02:34:44 2.53 --- Modules/signalmodule.c 2000/08/19 03:37:52 *************** *** 667,672 **** --- 667,673 ---- PyOS_AfterFork(void) { #ifdef WITH_THREAD + PyEval_ReInitThreads(); main_thread = PyThread_get_thread_ident(); main_pid = getpid(); #endif Index: Parser/intrcheck.c
RCS file: /cvsroot/python/python/dist/src/Parser/intrcheck.c,v retrieving revision 2.39 diff -c -r2.39 intrcheck.c *** Parser/intrcheck.c 2000/07/31 15:28:04 2.39 --- Parser/intrcheck.c 2000/08/19 03:37:54 *************** *** 206,209 **** --- 206,212 ---- void PyOS_AfterFork(void) { + #ifdef WITH_THREAD + PyEval_ReInitThreads(); + #endif } Index: Python/ceval.c
RCS file: /cvsroot/python/python/dist/src/Python/ceval.c,v retrieving revision 2.191 diff -c -r2.191 ceval.c *** Python/ceval.c 2000/08/18 19:53:25 2.191 --- Python/ceval.c 2000/08/19 03:38:06
*** 142,147 **** --- 142,165 ---- Py_FatalError("PyEval_ReleaseThread: wrong thread state"); PyThread_release_lock(interpreter_lock); }
+
/* This function is called from PyOS_AfterFork to ensure that newly
created child processes don't hold locks referring to threads which
are not running in the child process. (This could also be done using
pthread_atfork mechanism, at least for the pthreads implementation) */
void
PyEval_ReInitThreads(void)
{
if (!interpreter_lock)
return;
/*XXX Can't use PyThread_free_lock here because it does too
much error-checking. Doing this cleanly would require
adding a new function to each thread_*.h. Instead, just
create a new lock and waste a little bit of memory */
interpreter_lock = PyThread_allocate_lock();
PyThread_acquire_lock(interpreter_lock, 1);
main_thread = PyThread_get_thread_ident();
} #endif
/* Functions save_thread and restore_thread are always defined so
- Previous message: [Python-Dev] RE: [Patches] [Patch #101055] Cookie.py
- Next message: [Python-Dev] Re: Eureka! (Re: test_fork fails --with-thread)
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]