]> granicus.if.org Git - python/commitdiff
Issue #17094: Clear stale thread states after fork().
authorAntoine Pitrou <solipsis@pitrou.net>
Sun, 5 May 2013 21:47:09 +0000 (23:47 +0200)
committerAntoine Pitrou <solipsis@pitrou.net>
Sun, 5 May 2013 21:47:09 +0000 (23:47 +0200)
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).

Include/pystate.h
Lib/test/test_threading.py
Misc/NEWS
Python/ceval.c
Python/pystate.c

index b29ce2a482f340032ef4b1bae3c9bb1d1701f782..a8fcc73bcb3b4ad97ad0f39bd24e34312333c3db 100644 (file)
@@ -139,6 +139,7 @@ PyAPI_FUNC(PyThreadState *) _PyThreadState_Prealloc(PyInterpreterState *);
 PyAPI_FUNC(void) _PyThreadState_Init(PyThreadState *);
 PyAPI_FUNC(void) PyThreadState_Clear(PyThreadState *);
 PyAPI_FUNC(void) PyThreadState_Delete(PyThreadState *);
+PyAPI_FUNC(void) _PyThreadState_DeleteExcept(PyThreadState *tstate);
 #ifdef WITH_THREAD
 PyAPI_FUNC(void) PyThreadState_DeleteCurrent(void);
 PyAPI_FUNC(void) _PyGILState_Reinit(void);
index 9ed6ca7a168e1c0dfffb60ae9160697255590acd..ecf22fc7494c7a676ddb7af86fd5528f274c21af 100644 (file)
@@ -728,6 +728,31 @@ class ThreadJoinOnShutdown(BaseTestCase):
         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
index 70e8f2130a47219f41ae74b5cf53ab75bf363240..0ff2e17e1c42423e1ca49f3a31809fe9e8f62868 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -10,6 +10,11 @@ What's New in Python 3.4.0 Alpha 1?
 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
   the interpreter is shutdown and then started again.
 
index cbc0fabd032416e14f7afbf3c64decd3ddac756f..d32b6fbf5846aa482563cf5bbea56e3b4d6fa89b 100644 (file)
@@ -362,29 +362,28 @@ PyEval_ReleaseThread(PyThreadState *tstate)
     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();
 
     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 */
@@ -397,6 +396,9 @@ PyEval_ReInitThreads(void)
     else
         Py_DECREF(result);
     Py_DECREF(threading);
+
+    /* Destroy all threads except the current one */
+    _PyThreadState_DeleteExcept(current_tstate);
 }
 
 #else
index 70038936d6741c7f3332207ae9b0faaf5f5960af..2a6f16c87f119175ab1780211f7c671a07dd4a02 100644 (file)
@@ -414,6 +414,53 @@ PyThreadState_DeleteCurrent()
 #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_Get(void)
 {