summaryrefslogtreecommitdiffstats
path: root/win/tclWinConsole.c
diff options
context:
space:
mode:
Diffstat (limited to 'win/tclWinConsole.c')
-rw-r--r--win/tclWinConsole.c175
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);
}
/*