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