Tcl Source Code

Artifact [7db1608976]
Login

Artifact 7db16089766c834d575daa83d51666092d3fe6f9:

Attachment "tcl.all_threads_blocked.patch" to ticket [8bd13f07bd] added by stanko 2014-02-18 19:21:43. (unpublished)
Index: win/tclWinConsole.c
==================================================================
--- win/tclWinConsole.c
+++ win/tclWinConsole.c
@@ -49,10 +49,12 @@
  * threads.
  */
 
 typedef struct ConsoleThreadInfo {
     HANDLE thread;		/* Handle to reader or writer thread. */
+    HANDLE threadInitialized;	/* Manual-reset event to signal that thread has been initialized. */
+    int threadExiting;		/* Boolean indicating that thread is exiting. */
     HANDLE readyEvent;		/* Manual-reset event to signal _to_ the main
 				 * thread when the worker thread has finished
 				 * waiting for its normal work to happen. */
     HANDLE startEvent;		/* Auto-reset event used by the main thread to
 				 * signal when the thread should attempt to do
@@ -523,12 +525,15 @@
     DWORD id;
 
     threadInfoPtr->readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
     threadInfoPtr->startEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     threadInfoPtr->stopEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+    threadInfoPtr->threadInitialized = CreateEvent(NULL, TRUE, FALSE, NULL);
+    threadInfoPtr->threadExiting = FALSE;
     threadInfoPtr->thread = CreateThread(NULL, 256, threadProc, infoPtr, 0,
 	    &id);
+    WaitForSingleObject(threadInfoPtr->threadInitialized, INFINITE); /* wait for thread to initialize */
     SetThreadPriority(threadInfoPtr->thread, THREAD_PRIORITY_HIGHEST);
 }
 
 static void
 StopChannelThread(
@@ -558,15 +563,32 @@
 	    /*
 	     * Forcibly terminate the background thread as a last resort.
 	     * Note that we need to guard against terminating the thread while
 	     * it is in the middle of Tcl_ThreadAlert because it won't be able
 	     * to release the notifier lock.
+	     *
+	     * Also note that terminating threads during their initialization or teardown phase
+	     * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
+	     * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
+	     * LdrpInitializeThread() is executed within new threads to perform
+	     * initialization and to execute DllMain() of all loaded dlls.
+	     * As a result, all new threads are deadlocked in their initialization phase and never execute,
+	     * even though CreateThread() reports successful thread creation.
+	     * This results in a very weird process-wide behavior, which is extremely hard to debug.
+	     * 
+	     * THREADS SHOULD NEVER BE TERMINATED. Period.
+	     * 
+	     * But for now, check if thread is exiting, and if so, let it die peacefully.
 	     */
 
 	    Tcl_MutexLock(&consoleMutex);
-	    /* BUG: this leaks memory. */
-	    TerminateThread(threadInfoPtr->thread, 0);
+	    if ( threadInfoPtr->threadExiting ) {
+		    WaitForSingleObject(threadInfoPtr->thread, INFINITE);
+	    } else {
+		    /* BUG: this leaks memory. */
+		    TerminateThread(threadInfoPtr->thread, 0);
+	    }
 	    Tcl_MutexUnlock(&consoleMutex);
 	}
     }
 
     /*
@@ -573,10 +595,11 @@
      * Close all the handles associated with the thread, and set the thread
      * handle field to NULL to mark that the thread has been cleaned up.
      */
 
     CloseHandle(threadInfoPtr->thread);
+    CloseHandle(threadInfoPtr->threadInitialized);
     CloseHandle(threadInfoPtr->readyEvent);
     CloseHandle(threadInfoPtr->startEvent);
     CloseHandle(threadInfoPtr->stopEvent);
     threadInfoPtr->thread = NULL;
 }
@@ -1164,10 +1187,15 @@
     HANDLE *handle = infoPtr->handle;
     ConsoleThreadInfo *threadInfo = &infoPtr->reader;
     DWORD waitResult;
     HANDLE wEvents[2];
 
+    /*
+     * Notify StartChannelThread() that this thread is initialized
+     */
+    SetEvent(threadInfo->threadInitialized);
+
     /*
      * The first event takes precedence.
      */
 
     wEvents[0] = threadInfo->stopEvent;
@@ -1232,10 +1260,18 @@
 	    Tcl_ThreadAlert(infoPtr->threadId);
 	}
 	Tcl_MutexUnlock(&consoleMutex);
     }
 
+    /*
+     * Inform StopChannelThread() that this thread should not be terminated, since it is about to exit.
+     * See comment in StopChannelThread() for reasons.
+    */
+    Tcl_MutexLock(&consoleMutex);
+    threadInfo->threadExiting = TRUE;
+    Tcl_MutexUnlock(&consoleMutex);
+
     return 0;
 }
 
 /*
  *----------------------------------------------------------------------
@@ -1265,10 +1301,15 @@
     ConsoleThreadInfo *threadInfo = &infoPtr->writer;
     DWORD count, toWrite, waitResult;
     char *buf;
     HANDLE wEvents[2];
 
+    /*
+     * Notify StartChannelThread() that this thread is initialized
+     */
+    SetEvent(threadInfo->threadInitialized);
+
     /*
      * The first event takes precedence.
      */
 
     wEvents[0] = threadInfo->stopEvent;
@@ -1330,10 +1371,18 @@
 	    Tcl_ThreadAlert(infoPtr->threadId);
 	}
 	Tcl_MutexUnlock(&consoleMutex);
     }
 
+    /*
+     * Inform StopChannelThread() that this thread should not be terminated, since it is about to exit.
+     * See comment in StopChannelThread() for reasons.
+    */
+    Tcl_MutexLock(&consoleMutex);
+    threadInfo->threadExiting = TRUE;
+    Tcl_MutexUnlock(&consoleMutex);
+
     return 0;
 }
 
 /*
  *----------------------------------------------------------------------

Index: win/tclWinPipe.c
==================================================================
--- win/tclWinPipe.c
+++ win/tclWinPipe.c
@@ -103,10 +103,14 @@
     Tcl_ThreadId threadId;	/* Thread to which events should be reported.
 				 * This value is used by the reader/writer
 				 * threads. */
     HANDLE writeThread;		/* Handle to writer thread. */
     HANDLE readThread;		/* Handle to reader thread. */
+    HANDLE writeThreadInitialized;	/* Manual-reset event to signal that writer thread has been initialized */
+    HANDLE readThreadInitialized;	/* Manual-reset event to signal that reader thread has been initialized */
+    int writeThreadExiting;	/* Boolean indicating that write thread is exiting */
+    int readThreadExiting;	/* Boolean indicating that read thread is exiting */
     HANDLE writable;		/* Manual-reset event to signal when the
 				 * writer thread has finished waiting for the
 				 * current buffer to be written. */
     HANDLE readable;		/* Manual-reset event to signal when the
 				 * reader thread has finished waiting for
@@ -1592,12 +1596,15 @@
 	 */
 
 	infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
 	infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
 	infoPtr->stopReader = CreateEvent(NULL, TRUE, FALSE, NULL);
+	infoPtr->readThreadInitialized = CreateEvent(NULL, TRUE, FALSE, NULL);
+	infoPtr->readThreadExiting = FALSE;
 	infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread,
 		infoPtr, 0, &id);
+	WaitForSingleObject(infoPtr->readThreadInitialized, INFINITE); /* wait for thread to initialize */
 	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
 	infoPtr->validMask |= TCL_READABLE;
     } else {
 	infoPtr->readThread = 0;
     }
@@ -1607,12 +1614,15 @@
 	 */
 
 	infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
 	infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
 	infoPtr->stopWriter = CreateEvent(NULL, TRUE, FALSE, NULL);
+	infoPtr->writeThreadInitialized = CreateEvent(NULL, TRUE, FALSE, NULL);
+	infoPtr->writeThreadExiting = FALSE;
 	infoPtr->writeThread = CreateThread(NULL, 256, PipeWriterThread,
 		infoPtr, 0, &id);
+	WaitForSingleObject(infoPtr->writeThreadInitialized, INFINITE); /* wait for thread to initialize */
 	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
 	infoPtr->validMask |= TCL_WRITABLE;
     }
 
     /*
@@ -1844,21 +1854,38 @@
 		     * until a better solution is found.
 		     *
 		     * Note that we need to guard against terminating the
 		     * thread while it is in the middle of Tcl_ThreadAlert
 		     * because it won't be able to release the notifier lock.
+		     *
+		     * Also note that terminating threads during their initialization or teardown phase
+		     * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
+		     * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
+		     * LdrpInitializeThread() is executed within new threads to perform
+		     * initialization and to execute DllMain() of all loaded dlls.
+		     * As a result, all new threads are deadlocked in their initialization phase and never execute,
+		     * even though CreateThread() reports successful thread creation.
+		     * This results in a very weird process-wide behavior, which is extremely hard to debug.
+		     * 
+		     * THREADS SHOULD NEVER BE TERMINATED. Period.
+		     * 
+		     * But for now, check if thread is exiting, and if so, let it die peacefully.
 		     */
 
 		    Tcl_MutexLock(&pipeMutex);
-
-		    /* BUG: this leaks memory */
-		    TerminateThread(pipePtr->readThread, 0);
+		    if ( pipePtr->readThreadExiting ) {
+			    WaitForSingleObject(pipePtr->readThread, INFINITE);
+		    } else {
+			    /* BUG: this leaks memory */
+			    TerminateThread(pipePtr->readThread, 0);
+		    }
 		    Tcl_MutexUnlock(&pipeMutex);
 		}
 	    }
 
 	    CloseHandle(pipePtr->readThread);
+	    CloseHandle(pipePtr->readThreadInitialized);
 	    CloseHandle(pipePtr->readable);
 	    CloseHandle(pipePtr->startReader);
 	    CloseHandle(pipePtr->stopReader);
 	    pipePtr->readThread = NULL;
 	}
@@ -1900,12 +1927,12 @@
 
 	    GetExitCodeThread(pipePtr->writeThread, &exitCode);
 
 	    if (exitCode == STILL_ACTIVE) {
 		/*
-		 * Set the stop event so that if the reader thread is blocked
-		 * in PipeReaderThread on WaitForMultipleEvents, it will exit
+		 * Set the stop event so that if the writer thread is blocked
+		 * in PipeWriterThread on WaitForMultipleEvents, it will exit
 		 * cleanly.
 		 */
 
 		SetEvent(pipePtr->stopWriter);
 
@@ -1926,21 +1953,38 @@
 		     * until a better solution is found.
 		     *
 		     * Note that we need to guard against terminating the
 		     * thread while it is in the middle of Tcl_ThreadAlert
 		     * because it won't be able to release the notifier lock.
+		     *
+		     * Also note that terminating threads during their initialization or teardown phase
+		     * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
+		     * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
+		     * LdrpInitializeThread() is executed within new threads to perform
+		     * initialization and to execute DllMain() of all loaded dlls.
+		     * As a result, all new threads are deadlocked in their initialization phase and never execute,
+		     * even though CreateThread() reports successful thread creation.
+		     * This results in a very weird process-wide behavior, which is extremely hard to debug.
+		     * 
+		     * THREADS SHOULD NEVER BE TERMINATED. Period.
+		     * 
+		     * But for now, check if thread is exiting, and if so, let it die peacefully.
 		     */
 
 		    Tcl_MutexLock(&pipeMutex);
-
-		    /* BUG: this leaks memory */
-		    TerminateThread(pipePtr->writeThread, 0);
+		    if ( pipePtr->writeThreadExiting ) {
+			    WaitForSingleObject(pipePtr->writeThread, INFINITE);
+		    } else {
+			    /* BUG: this leaks memory */
+			    TerminateThread(pipePtr->writeThread, 0);
+		    }
 		    Tcl_MutexUnlock(&pipeMutex);
 		}
 	    }
 
 	    CloseHandle(pipePtr->writeThread);
+	    CloseHandle(pipePtr->writeThreadInitialized);
 	    CloseHandle(pipePtr->writable);
 	    CloseHandle(pipePtr->startWriter);
 	    CloseHandle(pipePtr->stopWriter);
 	    pipePtr->writeThread = NULL;
 	}
@@ -2812,10 +2856,15 @@
     DWORD count, err;
     int done = 0;
     HANDLE wEvents[2];
     DWORD waitResult;
 
+    /*
+     * Let TclpCreateCommandChannel() know that this thread has been initialized
+     */
+    SetEvent(infoPtr->readThreadInitialized);
+    
     wEvents[0] = infoPtr->stopReader;
     wEvents[1] = infoPtr->startReader;
 
     while (!done) {
 	/*
@@ -2904,10 +2953,18 @@
 	    Tcl_ThreadAlert(infoPtr->threadId);
 	}
 	Tcl_MutexUnlock(&pipeMutex);
     }
 
+    /*
+     * Inform PipeClose2Proc() that this thread should not be terminated, since it is about to exit.
+     * See comment in PipeClose2Proc() for reasons.
+    */
+    Tcl_MutexLock(&pipeMutex);
+    infoPtr->readThreadExiting = TRUE;
+    Tcl_MutexUnlock(&pipeMutex);
+
     return 0;
 }
 
 /*
  *----------------------------------------------------------------------
@@ -2936,10 +2993,15 @@
     char *buf;
     int done = 0;
     HANDLE wEvents[2];
     DWORD waitResult;
 
+    /*
+     * Let TclpCreateCommandChannel() know that this thread has been initialized
+     */
+    SetEvent(infoPtr->writeThreadInitialized);
+
     wEvents[0] = infoPtr->stopWriter;
     wEvents[1] = infoPtr->startWriter;
 
     while (!done) {
 	/*
@@ -3002,10 +3064,18 @@
 	    Tcl_ThreadAlert(infoPtr->threadId);
 	}
 	Tcl_MutexUnlock(&pipeMutex);
     }
 
+    /*
+     * Inform PipeClose2Proc() that this thread should not be terminated, since it is about to exit.
+     * See comment in PipeClose2Proc() for reasons.
+    */
+    Tcl_MutexLock(&pipeMutex);
+    infoPtr->writeThreadExiting = TRUE;
+    Tcl_MutexUnlock(&pipeMutex);
+
     return 0;
 }
 
 /*
  *----------------------------------------------------------------------

Index: win/tclWinSerial.c
==================================================================
--- win/tclWinSerial.c
+++ win/tclWinSerial.c
@@ -92,10 +92,12 @@
 				 * This value is used by the reader/writer
 				 * threads. */
     OVERLAPPED osRead;		/* OVERLAPPED structure for read operations. */
     OVERLAPPED osWrite;		/* OVERLAPPED structure for write operations */
     HANDLE writeThread;		/* Handle to writer thread. */
+    HANDLE writeThreadInitialized;	/* Manual-reset event to signal that thread has been initialized. */
+    int writeThreadExiting;		/* Boolean indicating that thread is exiting. */
     CRITICAL_SECTION csWrite;	/* Writer thread synchronisation. */
     HANDLE evWritable;		/* Manual-reset event to signal when the
 				 * writer thread has finished waiting for the
 				 * current buffer to be written. */
     HANDLE evStartWriter;	/* Auto-reset event used by the main thread to
@@ -641,22 +643,38 @@
 		/*
 		 * Forcibly terminate the background thread as a last resort.
 		 * Note that we need to guard against terminating the thread
 		 * while it is in the middle of Tcl_ThreadAlert because it
 		 * won't be able to release the notifier lock.
+		 *
+		 * Also note that terminating threads during their initialization or teardown phase
+		 * may result in ntdll.dll's LoaderLock to remain locked indefinitely.
+		 * This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock.
+		 * LdrpInitializeThread() is executed within new threads to perform
+		 * initialization and to execute DllMain() of all loaded dlls.
+		 * As a result, all new threads are deadlocked in their initialization phase and never execute,
+		 * even though CreateThread() reports successful thread creation.
+		 * This results in a very weird process-wide behavior, which is extremely hard to debug.
+		 * 
+		 * THREADS SHOULD NEVER BE TERMINATED. Period.
+		 * 
+		 * But for now, check if thread is exiting, and if so, let it die peacefully.
 		 */
 
 		Tcl_MutexLock(&serialMutex);
-
-		/* BUG: this leaks memory */
-		TerminateThread(serialPtr->writeThread, 0);
-
+		if ( serialPtr->writeThreadExiting ) {
+			WaitForSingleObject(serialPtr->writeThread, INFINITE);
+		} else {
+			/* BUG: this leaks memory. */
+			TerminateThread(serialPtr->writeThread, 0);
+		}
 		Tcl_MutexUnlock(&serialMutex);
 	    }
 	}
 
 	CloseHandle(serialPtr->writeThread);
+	CloseHandle(serialPtr->writeThreadInitialized);
 	CloseHandle(serialPtr->osWrite.hEvent);
 	CloseHandle(serialPtr->evWritable);
 	CloseHandle(serialPtr->evStartWriter);
 	CloseHandle(serialPtr->evStopWriter);
 	serialPtr->writeThread = NULL;
@@ -1317,10 +1335,15 @@
     DWORD bytesWritten, toWrite, waitResult;
     char *buf;
     OVERLAPPED myWrite;		/* Have an own OVERLAPPED in this thread. */
     HANDLE wEvents[2];
 
+    /*
+     * Notify TclWinOpenSerialChannel() that this thread is initialized
+     */
+    SetEvent(infoPtr->writeThreadInitialized);
+
     /*
      * The stop event takes precedence by being first in the list.
      */
 
     wEvents[0] = infoPtr->evStopWriter;
@@ -1402,10 +1425,19 @@
 	    Tcl_ThreadAlert(infoPtr->threadId);
 	}
 	Tcl_MutexUnlock(&serialMutex);
     }
 
+
+    /*
+     * Inform SerialCloseProc() that this thread should not be terminated, since it is about to exit.
+     * See comment in SerialCloseProc() for reasons.
+    */
+    Tcl_MutexLock(&serialMutex);
+    infoPtr->writeThreadExiting = TRUE;
+    Tcl_MutexUnlock(&serialMutex);
+
     return 0;
 }
 
 /*
  *----------------------------------------------------------------------
@@ -1524,12 +1556,15 @@
 
 	infoPtr->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
 	infoPtr->evWritable = CreateEvent(NULL, TRUE, TRUE, NULL);
 	infoPtr->evStartWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
 	infoPtr->evStopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
+	infoPtr->writeThreadInitialized = CreateEvent(NULL, TRUE, FALSE, NULL);
+	infoPtr->writeThreadExiting = FALSE;
 	infoPtr->writeThread = CreateThread(NULL, 256, SerialWriterThread,
 		infoPtr, 0, &id);
+	WaitForSingleObject(infoPtr->writeThreadInitialized, INFINITE); /* wait for thread to initialize */
     }
 
     /*
      * Files have default translation of AUTO and ^Z eof char, which means
      * that a ^Z will be accepted as EOF when reading.