diff options
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); } /* |