Issue #17094: Clear stale thread states after fork().
Note that this is a potentially disruptive change since it may release some system resources which would otherwise remain perpetually alive (e.g. database connections kept in thread-local storage).
This commit is contained in:
parent
39b17c513a
commit
8408cea0cd
|
@ -139,6 +139,7 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_Prealloc(PyInterpreterState *);
|
||||||
PyAPI_FUNC(void) _PyThreadState_Init(PyThreadState *);
|
PyAPI_FUNC(void) _PyThreadState_Init(PyThreadState *);
|
||||||
PyAPI_FUNC(void) PyThreadState_Clear(PyThreadState *);
|
PyAPI_FUNC(void) PyThreadState_Clear(PyThreadState *);
|
||||||
PyAPI_FUNC(void) PyThreadState_Delete(PyThreadState *);
|
PyAPI_FUNC(void) PyThreadState_Delete(PyThreadState *);
|
||||||
|
PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate);
|
||||||
#ifdef WITH_THREAD
|
#ifdef WITH_THREAD
|
||||||
PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
|
PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
|
||||||
PyAPI_FUNC(void) _PyGILState_Reinit(void);
|
PyAPI_FUNC(void) _PyGILState_Reinit(void);
|
||||||
|
|
|
@ -728,6 +728,31 @@ class ThreadJoinOnShutdown(BaseTestCase):
|
||||||
for t in threads:
|
for t in threads:
|
||||||
t.join()
|
t.join()
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, 'fork'), "needs os.fork()")
|
||||||
|
def test_clear_threads_states_after_fork(self):
|
||||||
|
# Issue #17094: check that threads states are cleared after fork()
|
||||||
|
|
||||||
|
# start a bunch of threads
|
||||||
|
threads = []
|
||||||
|
for i in range(16):
|
||||||
|
t = threading.Thread(target=lambda : time.sleep(0.3))
|
||||||
|
threads.append(t)
|
||||||
|
t.start()
|
||||||
|
|
||||||
|
pid = os.fork()
|
||||||
|
if pid == 0:
|
||||||
|
# check that threads states have been cleared
|
||||||
|
if len(sys._current_frames()) == 1:
|
||||||
|
os._exit(0)
|
||||||
|
else:
|
||||||
|
os._exit(1)
|
||||||
|
else:
|
||||||
|
_, status = os.waitpid(pid, 0)
|
||||||
|
self.assertEqual(0, status)
|
||||||
|
|
||||||
|
for t in threads:
|
||||||
|
t.join()
|
||||||
|
|
||||||
|
|
||||||
class ThreadingExceptionTests(BaseTestCase):
|
class ThreadingExceptionTests(BaseTestCase):
|
||||||
# A RuntimeError should be raised if Thread.start() is called
|
# A RuntimeError should be raised if Thread.start() is called
|
||||||
|
|
|
@ -10,6 +10,11 @@ What's New in Python 3.4.0 Alpha 1?
|
||||||
Core and Builtins
|
Core and Builtins
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
|
- Issue #17094: Clear stale thread states after fork(). Note that this
|
||||||
|
is a potentially disruptive change since it may release some system
|
||||||
|
resources which would otherwise remain perpetually alive (e.g. database
|
||||||
|
connections kept in thread-local storage).
|
||||||
|
|
||||||
- Issue #17408: Avoid using an obsolete instance of the copyreg module when
|
- Issue #17408: Avoid using an obsolete instance of the copyreg module when
|
||||||
the interpreter is shutdown and then started again.
|
the interpreter is shutdown and then started again.
|
||||||
|
|
||||||
|
|
|
@ -362,29 +362,28 @@ PyEval_ReleaseThread(PyThreadState *tstate)
|
||||||
drop_gil(tstate);
|
drop_gil(tstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* This function is called from PyOS_AfterFork to ensure that newly
|
/* This function is called from PyOS_AfterFork to destroy all threads which are
|
||||||
created child processes don't hold locks referring to threads which
|
* not running in the child process, and clear internal locks which might be
|
||||||
are not running in the child process. (This could also be done using
|
* held by those threads. (This could also be done using pthread_atfork
|
||||||
pthread_atfork mechanism, at least for the pthreads implementation.) */
|
* mechanism, at least for the pthreads implementation.) */
|
||||||
|
|
||||||
void
|
void
|
||||||
PyEval_ReInitThreads(void)
|
PyEval_ReInitThreads(void)
|
||||||
{
|
{
|
||||||
_Py_IDENTIFIER(_after_fork);
|
_Py_IDENTIFIER(_after_fork);
|
||||||
PyObject *threading, *result;
|
PyObject *threading, *result;
|
||||||
PyThreadState *tstate = PyThreadState_GET();
|
PyThreadState *current_tstate = PyThreadState_GET();
|
||||||
|
|
||||||
if (!gil_created())
|
if (!gil_created())
|
||||||
return;
|
return;
|
||||||
recreate_gil();
|
recreate_gil();
|
||||||
pending_lock = PyThread_allocate_lock();
|
pending_lock = PyThread_allocate_lock();
|
||||||
take_gil(tstate);
|
take_gil(current_tstate);
|
||||||
main_thread = PyThread_get_thread_ident();
|
main_thread = PyThread_get_thread_ident();
|
||||||
|
|
||||||
/* Update the threading module with the new state.
|
/* Update the threading module with the new state.
|
||||||
*/
|
*/
|
||||||
tstate = PyThreadState_GET();
|
threading = PyMapping_GetItemString(current_tstate->interp->modules,
|
||||||
threading = PyMapping_GetItemString(tstate->interp->modules,
|
|
||||||
"threading");
|
"threading");
|
||||||
if (threading == NULL) {
|
if (threading == NULL) {
|
||||||
/* threading not imported */
|
/* threading not imported */
|
||||||
|
@ -397,6 +396,9 @@ PyEval_ReInitThreads(void)
|
||||||
else
|
else
|
||||||
Py_DECREF(result);
|
Py_DECREF(result);
|
||||||
Py_DECREF(threading);
|
Py_DECREF(threading);
|
||||||
|
|
||||||
|
/* Destroy all threads except the current one */
|
||||||
|
_PyThreadState_DeleteExcept(current_tstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -414,6 +414,53 @@ PyThreadState_DeleteCurrent()
|
||||||
#endif /* WITH_THREAD */
|
#endif /* WITH_THREAD */
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Delete all thread states except the one passed as argument.
|
||||||
|
* Note that, if there is a current thread state, it *must* be the one
|
||||||
|
* passed as argument. Also, this won't touch any other interpreters
|
||||||
|
* than the current one, since we don't know which thread state should
|
||||||
|
* be kept in those other interpreteres.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
_PyThreadState_DeleteExcept(PyThreadState *tstate)
|
||||||
|
{
|
||||||
|
PyInterpreterState *interp = tstate->interp;
|
||||||
|
PyThreadState *p, *next, *garbage;
|
||||||
|
HEAD_LOCK();
|
||||||
|
/* Remove all thread states, except tstate, from the linked list of
|
||||||
|
thread states. This will allow calling PyThreadState_Clear()
|
||||||
|
without holding the lock.
|
||||||
|
XXX This would be simpler with a doubly-linked list. */
|
||||||
|
garbage = interp->tstate_head;
|
||||||
|
interp->tstate_head = tstate;
|
||||||
|
if (garbage == tstate) {
|
||||||
|
garbage = garbage->next;
|
||||||
|
tstate->next = NULL;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
for (p = garbage; p; p = p->next) {
|
||||||
|
if (p->next == tstate) {
|
||||||
|
p->next = tstate->next;
|
||||||
|
tstate->next = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tstate->next != NULL)
|
||||||
|
Py_FatalError("_PyThreadState_DeleteExcept: tstate not found "
|
||||||
|
"in interpreter thread states");
|
||||||
|
HEAD_UNLOCK();
|
||||||
|
/* Clear and deallocate all stale thread states. Even if this
|
||||||
|
executes Python code, we should be safe since it executes
|
||||||
|
in the current thread, not one of the stale threads. */
|
||||||
|
for (p = garbage; p; p = next) {
|
||||||
|
next = p->next;
|
||||||
|
PyThreadState_Clear(p);
|
||||||
|
free(p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
PyThreadState *
|
PyThreadState *
|
||||||
PyThreadState_Get(void)
|
PyThreadState_Get(void)
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue