diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -728,6 +728,31 @@ for t in threads: 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): # A RuntimeError should be raised if Thread.start() is called diff --git a/Python/ceval.c b/Python/ceval.c --- a/Python/ceval.c +++ b/Python/ceval.c @@ -364,29 +364,29 @@ drop_gil(tstate); } -/* 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.) */ +/* This function is called from PyOS_AfterFork to destroy all threads which are + * not running in the child process, and clear internal locks which might be + * held by those threads. (This could also be done using pthread_atfork + * mechanism, at least for the pthreads implementation.) */ void PyEval_ReInitThreads(void) { _Py_IDENTIFIER(_after_fork); PyObject *threading, *result; - PyThreadState *tstate = PyThreadState_GET(); + PyThreadState *current_tstate = PyThreadState_GET(); + PyThreadState *tstate, *tstate_next; if (!gil_created()) return; recreate_gil(); pending_lock = PyThread_allocate_lock(); - take_gil(tstate); + take_gil(current_tstate); main_thread = PyThread_get_thread_ident(); /* Update the threading module with the new state. */ - tstate = PyThreadState_GET(); - threading = PyMapping_GetItemString(tstate->interp->modules, + threading = PyMapping_GetItemString(current_tstate->interp->modules, "threading"); if (threading == NULL) { /* threading not imported */ @@ -399,6 +399,16 @@ else Py_DECREF(result); Py_DECREF(threading); + + /* destroy all threads except the current one */ + for (tstate = PyInterpreterState_ThreadHead(current_tstate->interp); + tstate; tstate = tstate_next) { + tstate_next = PyThreadState_Next(tstate); + if (tstate != current_tstate) { + PyThreadState_Clear(tstate); + PyThreadState_Delete(tstate); + } + } } #else