Attachment "tcl.all_threads_blocked.patch" to
ticket [8bd13f07bd]
added by
stanko
2014-02-18 19:21:43.
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.