diff options
author | davygrvy <davygrvy@pobox.com> | 2002-11-26 22:35:19 (GMT) |
---|---|---|
committer | davygrvy <davygrvy@pobox.com> | 2002-11-26 22:35:19 (GMT) |
commit | c0a30cfc05c2bb7dc8e9948c9869c54d847db951 (patch) | |
tree | 6765f4bcd26ec5a89888c057215ebf08b8646895 /win/tclWinConsole.c | |
parent | db8a753b2cace43e1aafe49067460d9c1599051b (diff) | |
download | tcl-c0a30cfc05c2bb7dc8e9948c9869c54d847db951.zip tcl-c0a30cfc05c2bb7dc8e9948c9869c54d847db951.tar.gz tcl-c0a30cfc05c2bb7dc8e9948c9869c54d847db951.tar.bz2 |
* win/tclWinConsole.c:
* win/tclWinPipe.c:
* win/tclWinSerial.c:
* win/tclWinSock.c:
* win/tclWinThrd.c:
* win/tclWinTime.c: General cleanup of all worker threads used
by the channel drivers. Eliminates the normal case where the
worker thread is terminated ('cept the winsock one). Instead,
use kernel events to signal a clean exit. Only when the worker
thread is blocked on an I/O call is the thread terminated.
Essentially, this makes all other channel worker threads behave
like the PipeReaderThread() function for it's cleaner exit
behavior. This appears to fix [Bug 597924] but needs 3rd party
confirmation to close the issue.
Diffstat (limited to 'win/tclWinConsole.c')
-rw-r--r-- | win/tclWinConsole.c | 175 |
1 files changed, 131 insertions, 44 deletions
diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c index 230a20e..7bb406b 100644 --- a/win/tclWinConsole.c +++ b/win/tclWinConsole.c @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinConsole.c,v 1.9 2002/11/07 02:13:37 mdejong Exp $ + * RCS: @(#) $Id: tclWinConsole.c,v 1.10 2002/11/26 22:35:20 davygrvy Exp $ */ #include "tclWinInt.h" @@ -79,9 +79,15 @@ typedef struct ConsoleInfo { HANDLE startWriter; /* Auto-reset event used by the main thread to * signal when the writer thread should attempt * to write to the console. */ + HANDLE stopWriter; /* Auto-reset event used by the main thread to + * signal when the writer thread should exit. + */ HANDLE startReader; /* Auto-reset event used by the main thread to * signal when the reader thread should attempt * to read from the console. */ + HANDLE stopReader; /* Auto-reset event used by the main thread to + * signal when the reader thread should exit. + */ DWORD writeError; /* An error caused by the last background * write. Set to 0 if no error has been * detected. This word is shared with the @@ -458,6 +464,7 @@ ConsoleCloseProc( int errorCode; ConsoleInfo *infoPtr, **nextPtrPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + DWORD exitCode; errorCode = 0; @@ -468,30 +475,50 @@ ConsoleCloseProc( */ if (consolePtr->readThread) { + /* - * Forcibly terminate the background thread. We cannot rely on the - * thread to cleanly terminate itself because we have no way of - * closing the handle without blocking in the case where the - * thread is in the middle of an I/O operation. 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. + * The thread may already have closed on it's own. Check it's + * exit code. */ - Tcl_MutexLock(&consoleMutex); - TerminateThread(consolePtr->readThread, 0); + GetExitCodeThread(consolePtr->readThread, &exitCode); - /* - * Wait for the thread to terminate. This ensures that we are - * completely cleaned up before we leave this function. - */ + if (exitCode == STILL_ACTIVE) { - WaitForSingleObject(consolePtr->readThread, INFINITE); - Tcl_MutexUnlock(&consoleMutex); + /* + * Set the stop event so that if the reader thread is blocked + * in ConsoleReaderThread on WaitForMultipleEvents, it will exit + * cleanly. + */ + + SetEvent(consolePtr->stopReader); + + /* + * Wait at most 20 milliseconds for the reader thread to close. + */ + + if (WaitForSingleObject(consolePtr->readThread, 20) + == WAIT_TIMEOUT) { + /* + * 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. + */ + + Tcl_MutexLock(&consoleMutex); + + /* BUG: this leaks memory. */ + TerminateThread(consolePtr->readThread, 0); + Tcl_MutexUnlock(&consoleMutex); + } + } CloseHandle(consolePtr->readThread); CloseHandle(consolePtr->readable); CloseHandle(consolePtr->startReader); + CloseHandle(consolePtr->stopReader); consolePtr->readThread = NULL; } consolePtr->validMask &= ~TCL_READABLE; @@ -512,29 +539,45 @@ ConsoleCloseProc( } /* - * Forcibly terminate the background thread. We cannot rely on the - * thread to cleanly terminate itself because we have no way of - * closing the handle without blocking in the case where the - * thread is in the middle of an I/O operation. 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. + * The thread may already have closed on it's own. Check it's + * exit code. */ - Tcl_MutexLock(&consoleMutex); - TerminateThread(consolePtr->writeThread, 0); + GetExitCodeThread(consolePtr->writeThread, &exitCode); - /* - * Wait for the thread to terminate. This ensures that we are - * completely cleaned up before we leave this function. - */ + if (exitCode == STILL_ACTIVE) { + /* + * Set the stop event so that if the reader thread is blocked + * in ConsoleWriterThread on WaitForMultipleEvents, it will + * exit cleanly. + */ - WaitForSingleObject(consolePtr->writeThread, INFINITE); - Tcl_MutexUnlock(&consoleMutex); + SetEvent(consolePtr->stopWriter); + + /* + * Wait at most 20 milliseconds for the writer thread to close. + */ + + if (WaitForSingleObject(consolePtr->writeThread, 20) + == WAIT_TIMEOUT) { + /* + * 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. + */ + + Tcl_MutexLock(&consoleMutex); + TerminateThread(consolePtr->writeThread, 0); + Tcl_MutexUnlock(&consoleMutex); + } + } CloseHandle(consolePtr->writeThread); CloseHandle(consolePtr->writable); CloseHandle(consolePtr->startWriter); + CloseHandle(consolePtr->stopWriter); consolePtr->writeThread = NULL; } consolePtr->validMask &= ~TCL_WRITABLE; @@ -1066,14 +1109,28 @@ ConsoleReaderThread(LPVOID arg) { ConsoleInfo *infoPtr = (ConsoleInfo *)arg; HANDLE *handle = infoPtr->handle; - DWORD count; + DWORD count, waitResult; + HANDLE wEvents[2]; + + /* The first event takes precedence. */ + wEvents[0] = infoPtr->stopReader; + wEvents[1] = infoPtr->startReader; for (;;) { /* * Wait for the main thread to signal before attempting to wait. */ - WaitForSingleObject(infoPtr->startReader, INFINITE); + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); + + if (waitResult != (WAIT_OBJECT_0 + 1)) { + /* + * The start event was not signaled. It must be the stop event + * or an error, so exit this thread. + */ + + break; + } count = 0; @@ -1081,7 +1138,7 @@ ConsoleReaderThread(LPVOID arg) * Look for data on the console, but first ignore any events * that are not KEY_EVENTs */ - if (ReadConsole(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, + if (ReadConsoleA(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, (LPDWORD) &infoPtr->bytesRead, NULL) != FALSE) { /* * Data was stored in the buffer. @@ -1114,7 +1171,8 @@ ConsoleReaderThread(LPVOID arg) Tcl_ThreadAlert(infoPtr->threadId); Tcl_MutexUnlock(&consoleMutex); } - return 0; /* NOT REACHED */ + + return 0; } /* @@ -1141,15 +1199,29 @@ ConsoleWriterThread(LPVOID arg) ConsoleInfo *infoPtr = (ConsoleInfo *)arg; HANDLE *handle = infoPtr->handle; - DWORD count, toWrite; + DWORD count, toWrite, waitResult; char *buf; + HANDLE wEvents[2]; + + /* The first event takes precedence. */ + wEvents[0] = infoPtr->stopWriter; + wEvents[1] = infoPtr->startWriter; for (;;) { /* * Wait for the main thread to signal before attempting to write. */ - WaitForSingleObject(infoPtr->startWriter, INFINITE); + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); + + if (waitResult != (WAIT_OBJECT_0 + 1)) { + /* + * The start event was not signaled. It must be the stop event + * or an error, so exit this thread. + */ + + break; + } buf = infoPtr->writeBuf; toWrite = infoPtr->toWrite; @@ -1159,7 +1231,7 @@ ConsoleWriterThread(LPVOID arg) */ while (toWrite > 0) { - if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) { + if (WriteConsoleA(handle, buf, toWrite, &count, NULL) == FALSE) { infoPtr->writeError = GetLastError(); break; } else { @@ -1185,7 +1257,8 @@ ConsoleWriterThread(LPVOID arg) Tcl_ThreadAlert(infoPtr->threadId); Tcl_MutexUnlock(&consoleMutex); } - return 0; /* NOT REACHED */ + + return 0; } @@ -1217,7 +1290,7 @@ TclWinOpenConsoleChannel(handle, channelName, permissions) char encoding[4 + TCL_INTEGER_SPACE]; ConsoleInfo *infoPtr; ThreadSpecificData *tsdPtr; - DWORD id; + DWORD id, modes; tsdPtr = ConsoleInit(); @@ -1232,7 +1305,7 @@ TclWinOpenConsoleChannel(handle, channelName, permissions) infoPtr->handle = handle; wsprintfA(encoding, "cp%d", GetConsoleCP()); - + /* * Use the pointer for the name of the result channel. * This keeps the channel names unique, since some may share @@ -1247,18 +1320,32 @@ TclWinOpenConsoleChannel(handle, channelName, permissions) infoPtr->threadId = Tcl_GetCurrentThread(); if (permissions & TCL_READABLE) { + /* + * Make sure the console input buffer is ready for only character + * input notifications and the buffer is set for line buffering. + * IOW, we only want to catch when complete lines are ready for + * reading. + */ + GetConsoleMode(infoPtr->handle, &modes); + modes &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT); + modes |= ENABLE_LINE_INPUT; + SetConsoleMode(infoPtr->handle, modes); + infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); - infoPtr->readThread = CreateThread(NULL, 8000, ConsoleReaderThread, + infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread, infoPtr, 0, &id); - SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); } if (permissions & TCL_WRITABLE) { infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); - infoPtr->writeThread = CreateThread(NULL, 8000, ConsoleWriterThread, + infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread, infoPtr, 0, &id); + SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST); } /* |