summaryrefslogtreecommitdiffstats
path: root/win/tclWinPipe.c
diff options
context:
space:
mode:
authorsebres <sebres@users.sourceforge.net>2017-04-05 10:40:29 (GMT)
committersebres <sebres@users.sourceforge.net>2017-04-05 10:40:29 (GMT)
commit9b1d320daf1b34b63dca5889e205d63cc874912d (patch)
tree9181e72e81b06cb0cbd35df7aa75a2135d356576 /win/tclWinPipe.c
parent224849c744f0f6ceb853162afd318b858835850c (diff)
downloadtcl-9b1d320daf1b34b63dca5889e205d63cc874912d.zip
tcl-9b1d320daf1b34b63dca5889e205d63cc874912d.tar.gz
tcl-9b1d320daf1b34b63dca5889e205d63cc874912d.tar.bz2
Contributed by "stanko" as patch within 8bd13f07bde6fb0631f27927e36461fdefe8ca95
Resolves blocking of pipes-thread (reader/writer) under huge last: Terminating threads during their initialization resp. teardown phase may result LoaderLock in the ntdll.dll's (to remain locked indefinitely). This causes ntdll.dll's LdrpInitializeThread() to deadlock trying to acquire LoaderLock. Possible fix for 9d75181ee70af318830e99ede6ebb5df72a9b079
Diffstat (limited to 'win/tclWinPipe.c')
-rw-r--r--win/tclWinPipe.c84
1 files changed, 78 insertions, 6 deletions
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c
index 4666deb..9677792 100644
--- a/win/tclWinPipe.c
+++ b/win/tclWinPipe.c
@@ -111,6 +111,10 @@ typedef struct PipeInfo {
* 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. */
@@ -1601,8 +1605,11 @@ TclpCreateCommandChannel(
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 {
@@ -1616,8 +1623,11 @@ TclpCreateCommandChannel(
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;
}
@@ -1853,17 +1863,35 @@ PipeClose2Proc(
* 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);
@@ -1909,8 +1937,8 @@ PipeClose2Proc(
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.
*/
@@ -1935,17 +1963,35 @@ PipeClose2Proc(
* 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);
@@ -2821,6 +2867,11 @@ PipeReaderThread(
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;
@@ -2913,6 +2964,14 @@ PipeReaderThread(
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;
}
@@ -2945,6 +3004,11 @@ PipeWriterThread(
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;
@@ -3011,6 +3075,14 @@ PipeWriterThread(
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;
}