Posted to tcl by dgp at Fri Apr 26 14:04:00 GMT 2013view pretty

Index: generic/tclEvent.c
==================================================================
--- generic/tclEvent.c
+++ generic/tclEvent.c
@@ -79,12 +79,10 @@
  * in closing of files and pipes.
  */
 
 static int inExit = 0;
 
-static int subsystemsInitialized = 0;
-
 /*
  * This variable contains the application wide exit handler. It will be called
  * by Tcl_Exit instead of the C-runtime exit if this variable is set to a
  * non-NULL value.
  */
@@ -1013,31 +1011,40 @@
  *     Varied, see the respective initialization routines.
  *
  *-------------------------------------------------------------------------
  */
 
+#define INITDEBUG 1
+typedef enum {
+    DOWN, CHANGING, UP
+} State;
+
+static State initState = DOWN;
+static Tcl_ThreadId changer = NULL;
+
 void
 TclInitSubsystems(void)
 {
-    if (inExit != 0) {
-       Tcl_Panic("TclInitSubsystems called while exiting");
+  start:
+    if (initState == UP) {
+       goto done;
     }
 
-    if (subsystemsInitialized == 0) {
+    if (initState == DOWN) {
        /*
-        * Double check inside the mutex. There are definitly calls back into
-        * this routine from some of the functions below.
+        * Enter the race to become the thread to change initState from
+        * DOWN to UP.
         */
 
        TclpInitLock();
-       if (subsystemsInitialized == 0) {
-           /*
-            * Have to set this bit here to avoid deadlock with the routines
-            * below us that call into TclInitSubsystems.
-            */
 
-           subsystemsInitialized = 1;
+       /* Did we win? */
+       if (initState == DOWN) {
+
+           /* Yes! Record that we are CHANGING the state */
+           initState = CHANGING;
+           changer = Tcl_GetCurrentThread();
 
            /*
             * Initialize locks used by the memory allocators before anything
             * interesting happens so we can use the allocators in the
             * implementation of self-initializing locks.
@@ -1059,13 +1066,43 @@
                                         * mutexes. */
            TclInitIOSubsystem();       /* Inits a tsd key (noop). */
            TclInitEncodingSubsystem(); /* Process wide encoding init. */
            TclpSetInterfaces();
            TclInitNamespaceSubsystem();/* Register ns obj type (mutexed). */
+
+           /* Record that the state is now UP */
+           initState = UP;
+           changer = NULL;
+       }
+       TclpInitUnlock();
+    } else {
+       /* initState != DOWN */
+       if (changer == Tcl_GetCurrentThread()) {
+           /*
+            * We're the thread changing state and must have reached here via
+            * a recursive call.  Return as a no-op to avoid deadlock.
+            * NOTE: This really should not be happening, and the caller
+            * responsible for this recursion is in danger of seeing a
+            * call to TclInitSubsystems() return even though initialization
+            * is not complete.  Consider adding a panic here, at least in
+            * some build configurations to hunt down and eliminate such
+            * things. 
+            */
+#if INITDEBUG
+           Tcl_Panic("recursion in TclInitSubsystems");
+#endif
+           return;
        }
+       /*
+        * A change of state is in progress in another thread.  Make
+        * sure we wait for it to finish before trying again.
+        */
+       TclpInitLock();
        TclpInitUnlock();
+       goto start;
     }
+  done:
     TclInitNotifier();
 }
 ^L
 /*
  *----------------------------------------------------------------------
@@ -1094,15 +1131,21 @@
      * Invoke exit handlers first.
      */
 
     InvokeExitHandlers();   
 
-    TclpInitLock();
-    if (subsystemsInitialized == 0) {
+    start:
+    if (initState == DOWN) {
        goto alreadyFinalized;
     }
-    subsystemsInitialized = 0;
+
+    if (initState == UP) {
+
+       TclpInitLock();
+       if (initState == UP) {
+           initState = CHANGING;
+           changer = Tcl_GetCurrentThread();
 
     /*
      * Ensure the thread-specific data is initialised as it is used in
      * Tcl_FinalizeThread()
      */
@@ -1249,10 +1292,26 @@
     /*
      * At this point, there should no longer be any ckalloc'ed memory.
      */
 
     TclFinalizeMemorySubsystem();
+
+    initState = DOWN;
+    changer = NULL;
+    }
+    TclpInitUnlock();
+    } else {
+       if (changer == Tcl_GetCurrentThread()) {
+#if INITDEBUG
+           Tcl_Panic("recursion in Tcl_Finalize()");
+#endif
+           return;
+       }
+       TclpInitLock();
+       TclpInitUnlock();
+       goto start;
+    }
 
   alreadyFinalized:
     TclFinalizeLock();
 }
 ^L