diff options
Diffstat (limited to 'win/tclWinConsole.c')
| -rw-r--r-- | win/tclWinConsole.c | 790 |
1 files changed, 495 insertions, 295 deletions
diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c index 2e364d1..361fb3d 100644 --- a/win/tclWinConsole.c +++ b/win/tclWinConsole.c @@ -1,22 +1,19 @@ -/* +/* * tclWinConsole.c -- * - * This file implements the Windows-specific console functions, - * and the "console" channel driver. + * This file implements the Windows-specific console functions, and the + * "console" channel driver. * * Copyright (c) 1999 by Scriptics Corp. * - * 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.6 2001/07/16 23:30:16 mdejong Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclWinInt.h" #include <fcntl.h> #include <io.h> -#include <sys/stat.h> /* * The following variable is used to tell whether this module has been @@ -45,10 +42,11 @@ TCL_DECLARE_MUTEX(consoleMutex) */ #define CONSOLE_EOF (1<<2) /* Console has reached EOF. */ -#define CONSOLE_BUFFERED (1<<3) /* data was read into a buffer by the reader - thread */ +#define CONSOLE_BUFFERED (1<<3) /* Data was read into a buffer by the reader + * thread. */ #define CONSOLE_BUFFER_SIZE (8*1024) + /* * This structure describes per-instance data for a console based channel. */ @@ -71,46 +69,47 @@ typedef struct ConsoleInfo { HANDLE writeThread; /* Handle to writer thread. */ HANDLE readThread; /* Handle to reader thread. */ HANDLE writable; /* Manual-reset event to signal when the - * writer thread has finished waiting for - * the current buffer to be written. */ + * 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 * input. */ HANDLE startWriter; /* Auto-reset event used by the main thread to - * signal when the writer thread should attempt - * to write to the console. */ + * 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. */ + * 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 + * write. Set to 0 if no error has been + * detected. This word is shared with the * writer thread so access must be - * synchronized with the writable object. - */ - char *writeBuf; /* Current background output buffer. - * Access is synchronized with the writable - * object. */ - int writeBufLen; /* Size of write buffer. Access is - * synchronized with the writable - * object. */ - int toWrite; /* Current amount to be written. Access is + * synchronized with the writable object. */ + char *writeBuf; /* Current background output buffer. Access is + * synchronized with the writable object. */ + int writeBufLen; /* Size of write buffer. Access is + * synchronized with the writable object. */ + int toWrite; /* Current amount to be written. Access is * synchronized with the writable object. */ int readFlags; /* Flags that are shared with the reader - * thread. Access is synchronized with the - * readable object. */ - int bytesRead; /* number of bytes in the buffer */ - int offset; /* number of bytes read out of the buffer */ + * thread. Access is synchronized with the + * readable object. */ + int bytesRead; /* number of bytes in the buffer */ + int offset; /* number of bytes read out of the buffer */ char buffer[CONSOLE_BUFFER_SIZE]; - /* Data consumed by reader thread. */ + /* Data consumed by reader thread. */ } ConsoleInfo; typedef struct ThreadSpecificData { /* - * The following pointer refers to the head of the list of consoles - * that are being watched for file events. + * The following pointer refers to the head of the list of consoles that + * are being watched for file events. */ - + ConsoleInfo *firstConsolePtr; } ThreadSpecificData; @@ -122,9 +121,9 @@ static Tcl_ThreadDataKey dataKey; */ typedef struct ConsoleEvent { - Tcl_Event header; /* Information that is standard for - * all events. */ - ConsoleInfo *infoPtr; /* Pointer to console info structure. Note + Tcl_Event header; /* Information that is standard for all + * events. */ + ConsoleInfo *infoPtr; /* Pointer to console info structure. Note * that we still have to verify that the * console exists before dereferencing this * pointer. */ @@ -134,7 +133,7 @@ typedef struct ConsoleEvent { * Declarations for functions used only in this file. */ -static int ConsoleBlockModeProc(ClientData instanceData, int mode); +static int ConsoleBlockModeProc(ClientData instanceData,int mode); static void ConsoleCheckProc(ClientData clientData, int flags); static int ConsoleCloseProc(ClientData instanceData, Tcl_Interp *interp); @@ -142,17 +141,19 @@ static int ConsoleEventProc(Tcl_Event *evPtr, int flags); static void ConsoleExitHandler(ClientData clientData); static int ConsoleGetHandleProc(ClientData instanceData, int direction, ClientData *handlePtr); -static ThreadSpecificData *ConsoleInit(void); +static void ConsoleInit(void); static int ConsoleInputProc(ClientData instanceData, char *buf, int toRead, int *errorCode); -static int ConsoleOutputProc(ClientData instanceData, char *buf, - int toWrite, int *errorCode); +static int ConsoleOutputProc(ClientData instanceData, + CONST char *buf, int toWrite, int *errorCode); static DWORD WINAPI ConsoleReaderThread(LPVOID arg); static void ConsoleSetupProc(ClientData clientData, int flags); static void ConsoleWatchProc(ClientData instanceData, int mask); static DWORD WINAPI ConsoleWriterThread(LPVOID arg); static void ProcExitHandler(ClientData clientData); static int WaitForRead(ConsoleInfo *infoPtr, int blocking); +static void ConsoleThreadActionProc(ClientData instanceData, + int action); /* * This structure describes the channel type structure for command console @@ -161,7 +162,7 @@ static int WaitForRead(ConsoleInfo *infoPtr, int blocking); static Tcl_ChannelType consoleChannelType = { "console", /* Type name. */ - TCL_CHANNEL_VERSION_2, /* v2 channel */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ ConsoleCloseProc, /* Close proc. */ ConsoleInputProc, /* Input proc. */ ConsoleOutputProc, /* Output proc. */ @@ -174,11 +175,57 @@ static Tcl_ChannelType consoleChannelType = { ConsoleBlockModeProc, /* Set blocking or non-blocking mode.*/ NULL, /* flush proc. */ NULL, /* handler proc. */ + NULL, /* wide seek proc */ + ConsoleThreadActionProc, /* thread action proc */ + NULL, /* truncation */ }; /* *---------------------------------------------------------------------- * + * readConsoleBytes, writeConsoleBytes -- + * Wrapper for ReadConsole{A,W}, that takes and returns number of bytes + * instead of number of TCHARS + */ +static BOOL +readConsoleBytes( + HANDLE hConsole, + LPVOID lpBuffer, + DWORD nbytes, + LPDWORD nbytesread) +{ + DWORD ntchars; + BOOL result; + int tcharsize; + tcharsize = tclWinProcs->useWide? 2 : 1; + result = tclWinProcs->readConsoleProc( + hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL); + if (nbytesread) + *nbytesread = (ntchars*tcharsize); + return result; +} + +static BOOL +writeConsoleBytes( + HANDLE hConsole, + const VOID *lpBuffer, + DWORD nbytes, + LPDWORD nbyteswritten) +{ + DWORD ntchars; + BOOL result; + int tcharsize; + tcharsize = tclWinProcs->useWide? 2 : 1; + result = tclWinProcs->writeConsoleProc( + hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL); + if (nbyteswritten) + *nbyteswritten = (ntchars*tcharsize); + return result; +} + +/* + *---------------------------------------------------------------------- + * * ConsoleInit -- * * This function initializes the static variables for this file. @@ -192,14 +239,14 @@ static Tcl_ChannelType consoleChannelType = { *---------------------------------------------------------------------- */ -static ThreadSpecificData * -ConsoleInit() +static void +ConsoleInit(void) { ThreadSpecificData *tsdPtr; /* - * Check the initialized flag first, then check again in the mutex. - * This is a speed enhancement. + * Check the initialized flag first, then check again in the mutex. This + * is a speed enhancement. */ if (!initialized) { @@ -218,7 +265,6 @@ ConsoleInit() Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL); Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL); } - return tsdPtr; } /* @@ -226,8 +272,8 @@ ConsoleInit() * * ConsoleExitHandler -- * - * This function is called to cleanup the console module before - * Tcl is unloaded. + * This function is called to cleanup the console module before Tcl is + * unloaded. * * Results: * None. @@ -250,8 +296,8 @@ ConsoleExitHandler( * * ProcExitHandler -- * - * This function is called to cleanup the process list before - * Tcl is unloaded. + * This function is called to cleanup the process list before Tcl is + * unloaded. * * Results: * None. @@ -276,8 +322,8 @@ ProcExitHandler( * * ConsoleSetupProc -- * - * This procedure is invoked before Tcl_DoOneEvent blocks waiting - * for an event. + * This procedure is invoked before Tcl_DoOneEvent blocks waiting for an + * event. * * Results: * None. @@ -301,12 +347,12 @@ ConsoleSetupProc( if (!(flags & TCL_FILE_EVENTS)) { return; } - + /* - * Look to see if any events are already pending. If they are, poll. + * Look to see if any events are already pending. If they are, poll. */ - for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; + for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { if (infoPtr->watchMask & TCL_WRITABLE) { if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { @@ -329,8 +375,8 @@ ConsoleSetupProc( * * ConsoleCheckProc -- * - * This procedure is called by Tcl_DoOneEvent to check the console - * event source for events. + * This procedure is called by Tcl_DoOneEvent to check the console event + * source for events. * * Results: * None. @@ -354,18 +400,18 @@ ConsoleCheckProc( if (!(flags & TCL_FILE_EVENTS)) { return; } - + /* * Queue events for any ready consoles that don't already have events * queued. */ - for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; + for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { if (infoPtr->flags & CONSOLE_PENDING) { continue; } - + /* * Queue an event if the console is signaled for reading or writing. */ @@ -376,7 +422,7 @@ ConsoleCheckProc( needEvent = 1; } } - + if (infoPtr->watchMask & TCL_READABLE) { if (WaitForRead(infoPtr, 0) >= 0) { needEvent = 1; @@ -414,15 +460,16 @@ static int ConsoleBlockModeProc( ClientData instanceData, /* Instance data for channel. */ int mode) /* TCL_MODE_BLOCKING or - * TCL_MODE_NONBLOCKING. */ + * TCL_MODE_NONBLOCKING. */ { ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; - + /* - * Consoles on Windows can not be switched between blocking and nonblocking, - * hence we have to emulate the behavior. This is done in the input - * function by checking against a bit in the state. We set or unset the - * bit here to cause the input function to emulate the correct behavior. + * Consoles on Windows can not be switched between blocking and + * nonblocking, hence we have to emulate the behavior. This is done in the + * input function by checking against a bit in the state. We set or unset + * the bit here to cause the input function to emulate the correct + * behavior. */ if (mode == TCL_MODE_NONBLOCKING) { @@ -458,89 +505,131 @@ ConsoleCloseProc( int errorCode; ConsoleInfo *infoPtr, **nextPtrPtr; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); + DWORD exitCode; errorCode = 0; - + /* - * Clean up the background thread if necessary. Note that this - * must be done before we can close the file, since the - * thread may be blocking trying to read from the console. + * Clean up the background thread if necessary. Note that this must be + * done before we can close the file, since the thread may be blocking + * trying to read from the console. */ - + 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) { + /* + * Set the stop event so that if the reader thread is blocked in + * ConsoleReaderThread on WaitForMultipleEvents, it will exit + * cleanly. + */ - WaitForSingleObject(consolePtr->readThread, INFINITE); - Tcl_MutexUnlock(&consoleMutex); + 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; /* - * Wait for the writer thread to finish the current buffer, then - * terminate the thread and close the handles. If the channel is - * nonblocking, there should be no pending write operations. + * Wait for the writer thread to finish the current buffer, then terminate + * the thread and close the handles. If the channel is nonblocking, there + * should be no pending write operations. */ - + if (consolePtr->writeThread) { - WaitForSingleObject(consolePtr->writable, INFINITE); + if (consolePtr->toWrite) { + /* + * We only need to wait if there is something to write. This may + * prevent infinite wait on exit. [python bug 216289] + */ + + WaitForSingleObject(consolePtr->writable, INFINITE); + } /* - * 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); + + /* BUG: this leaks memory. */ + 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; /* - * Don't close the Win32 handle if the handle is a standard channel - * during the exit process. Otherwise, one thread may kill the stdio - * of another. + * Don't close the Win32 handle if the handle is a standard channel during + * the thread exit process. Otherwise, one thread may kill the stdio of + * another. */ - if (!TclInExit() + if (!TclInThreadExit() || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle) && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle) && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) { @@ -549,7 +638,7 @@ ConsoleCloseProc( errorCode = errno; } } - + consolePtr->watchMask &= consolePtr->validMask; /* @@ -578,8 +667,8 @@ ConsoleCloseProc( * * ConsoleInputProc -- * - * Reads input from the IO channel into the buffer given. Returns - * count of how many bytes were actually read, and an error indication. + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. * * Results: * A count of how many bytes were read is returned and an error @@ -593,11 +682,11 @@ ConsoleCloseProc( static int ConsoleInputProc( - ClientData instanceData, /* Console state. */ - char *buf, /* Where to store data read. */ - int bufSize, /* How much space is available - * in the buffer? */ - int *errorCode) /* Where to store error code. */ + ClientData instanceData, /* Console state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available in the + * buffer? */ + int *errorCode) /* Where to store error code. */ { ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; DWORD count, bytesRead = 0; @@ -608,13 +697,13 @@ ConsoleInputProc( /* * Synchronize with the reader thread. */ - + result = WaitForRead(infoPtr, (infoPtr->flags & CONSOLE_ASYNC) ? 0 : 1); - + /* * If an error occurred, return immediately. */ - + if (result == -1) { *errorCode = errno; return -1; @@ -636,22 +725,22 @@ ConsoleInputProc( /* * Reset the buffer */ - + infoPtr->readFlags &= ~CONSOLE_BUFFERED; infoPtr->offset = 0; } return bytesRead; } - + /* - * Attempt to read bufSize bytes. The read will return immediately - * if there is any data available. Otherwise it will block until - * at least one byte is available or an EOF occurs. + * Attempt to read bufSize bytes. The read will return immediately if + * there is any data available. Otherwise it will block until at least one + * byte is available or an EOF occurs. */ - if (ReadConsole(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count, - (LPOVERLAPPED) NULL) == TRUE) { + if (readConsoleBytes(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count) + == TRUE) { buf[count] = '\0'; return count; } @@ -664,12 +753,12 @@ ConsoleInputProc( * * ConsoleOutputProc -- * - * Writes the given output on the IO channel. Returns count of how - * many characters were actually written, and an error indication. + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. * * Results: - * A count of how many characters were written is returned and an - * error indication is returned in an output argument. + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. * * Side effects: * Writes output on the actual channel. @@ -679,26 +768,26 @@ ConsoleInputProc( static int ConsoleOutputProc( - ClientData instanceData, /* Console state. */ - char *buf, /* The data buffer. */ - int toWrite, /* How many bytes to write? */ - int *errorCode) /* Where to store error code. */ + ClientData instanceData, /* Console state. */ + CONST char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ { ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; DWORD bytesWritten, timeout; - + *errorCode = 0; timeout = (infoPtr->flags & CONSOLE_ASYNC) ? 0 : INFINITE; if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { /* - * The writer thread is blocked waiting for a write to complete - * and the channel is in non-blocking mode. + * The writer thread is blocked waiting for a write to complete and + * the channel is in non-blocking mode. */ errno = EAGAIN; goto error; } - + /* * Check for a background error on the last write. */ @@ -724,31 +813,31 @@ ConsoleOutputProc( ckfree(infoPtr->writeBuf); } infoPtr->writeBufLen = toWrite; - infoPtr->writeBuf = ckalloc((unsigned int) toWrite); + infoPtr->writeBuf = ckalloc((size_t)toWrite); } - memcpy(infoPtr->writeBuf, buf, (size_t) toWrite); + memcpy(infoPtr->writeBuf, buf, (size_t)toWrite); infoPtr->toWrite = toWrite; ResetEvent(infoPtr->writable); SetEvent(infoPtr->startWriter); bytesWritten = toWrite; } else { /* - * In the blocking case, just try to write the buffer directly. - * This avoids an unnecessary copy. + * In the blocking case, just try to write the buffer directly. This + * avoids an unnecessary copy. */ - if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite, - &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { + if (writeConsoleBytes(infoPtr->handle, buf, (DWORD)toWrite, + &bytesWritten) + == FALSE) { TclWinConvertError(GetLastError()); goto error; } } return bytesWritten; - error: + error: *errorCode = errno; return -1; - } /* @@ -756,15 +845,15 @@ ConsoleOutputProc( * * ConsoleEventProc -- * - * This function is invoked by Tcl_ServiceEvent when a file event - * reaches the front of the event queue. This procedure invokes - * Tcl_NotifyChannel on the console. + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This procedure invokes Tcl_NotifyChannel + * on the console. * * Results: - * Returns 1 if the event was handled, meaning it should be removed - * from the queue. Returns 0 if the event was not handled, meaning - * it should stay on the queue. The only time the event isn't - * handled is if the TCL_FILE_EVENTS flag bit isn't set. + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. * * Side effects: * Whatever the notifier callback does. @@ -775,8 +864,8 @@ ConsoleOutputProc( static int ConsoleEventProc( Tcl_Event *evPtr, /* Event to service. */ - int flags) /* Flags that indicate what events to - * handle, such as TCL_FILE_EVENTS. */ + int flags) /* Flags that indicate what events to handle, + * such as TCL_FILE_EVENTS. */ { ConsoleEvent *consoleEvPtr = (ConsoleEvent *)evPtr; ConsoleInfo *infoPtr; @@ -789,9 +878,9 @@ ConsoleEventProc( /* * Search through the list of watched consoles for the one whose handle - * matches the event. We do this rather than simply dereferencing - * the handle in the event so that consoles can be deleted while the - * event is in the queue. + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that consoles can be deleted while the event is + * in the queue. */ for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL; @@ -811,15 +900,15 @@ ConsoleEventProc( } /* - * Check to see if the console is readable. Note - * that we can't tell if a console is writable, so we always report it - * as being writable unless we have detected EOF. + * Check to see if the console is readable. Note that we can't tell if a + * console is writable, so we always report it as being writable unless we + * have detected EOF. */ mask = 0; if (infoPtr->watchMask & TCL_WRITABLE) { if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { - mask = TCL_WRITABLE; + mask = TCL_WRITABLE; } } @@ -830,7 +919,7 @@ ConsoleEventProc( } else { mask |= TCL_READABLE; } - } + } } /* @@ -846,8 +935,7 @@ ConsoleEventProc( * * ConsoleWatchProc -- * - * Called by the notifier to set up to watch for events on this - * channel. + * Called by the notifier to set up to watch for events on this channel. * * Results: * None. @@ -860,10 +948,10 @@ ConsoleEventProc( static void ConsoleWatchProc( - ClientData instanceData, /* Console state. */ - int mask) /* What events to watch for, OR-ed - * combination of TCL_READABLE, - * TCL_WRITABLE and TCL_EXCEPTION. */ + ClientData instanceData, /* Console state. */ + int mask) /* What events to watch for, OR-ed combination + * of TCL_READABLE, TCL_WRITABLE and + * TCL_EXCEPTION. */ { ConsoleInfo **nextPtrPtr, *ptr; ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; @@ -871,9 +959,8 @@ ConsoleWatchProc( ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* - * Since most of the work is handled by the background threads, - * we just need to update the watchMask and then force the notifier - * to poll once. + * Since most of the work is handled by the background threads, we just + * need to update the watchMask and then force the notifier to poll once. */ infoPtr->watchMask = mask & infoPtr->validMask; @@ -884,19 +971,17 @@ ConsoleWatchProc( tsdPtr->firstConsolePtr = infoPtr; } Tcl_SetMaxBlockTime(&blockTime); - } else { - if (oldMask) { - /* - * Remove the console from the list of watched consoles. - */ + } else if (oldMask) { + /* + * Remove the console from the list of watched consoles. + */ - for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr; - ptr != NULL; - nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { - if (infoPtr == ptr) { - *nextPtrPtr = ptr->nextPtr; - break; - } + for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr; + ptr != NULL; + nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { + if (infoPtr == ptr) { + *nextPtrPtr = ptr->nextPtr; + break; } } } @@ -907,12 +992,12 @@ ConsoleWatchProc( * * ConsoleGetHandleProc -- * - * Called from Tcl_GetChannelHandle to retrieve OS handles from - * inside a command consoleline based channel. + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command consoleline based channel. * * Results: - * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if - * there is no handle for the specified direction. + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. * * Side effects: * None. @@ -924,7 +1009,7 @@ static int ConsoleGetHandleProc( ClientData instanceData, /* The console state. */ int direction, /* TCL_READABLE or TCL_WRITABLE */ - ClientData *handlePtr) /* Where to store the handle. */ + ClientData *handlePtr) /* Where to store the handle. */ { ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; @@ -937,69 +1022,68 @@ ConsoleGetHandleProc( * * WaitForRead -- * - * Wait until some data is available, the console is at - * EOF or the reader thread is blocked waiting for data (if the - * channel is in non-blocking mode). + * Wait until some data is available, the console is at EOF or the reader + * thread is blocked waiting for data (if the channel is in non-blocking + * mode). * * Results: - * Returns 1 if console is readable. Returns 0 if there is no data - * on the console, but there is buffered data. Returns -1 if an - * error occurred. If an error occurred, the threads may not - * be synchronized. + * Returns 1 if console is readable. Returns 0 if there is no data on the + * console, but there is buffered data. Returns -1 if an error occurred. + * If an error occurred, the threads may not be synchronized. * * Side effects: - * Updates the shared state flags. If no error occurred, - * the reader thread is blocked waiting for a signal from the - * main thread. + * Updates the shared state flags. If no error occurred, the reader + * thread is blocked waiting for a signal from the main thread. * *---------------------------------------------------------------------- */ static int WaitForRead( - ConsoleInfo *infoPtr, /* Console state. */ - int blocking) /* Indicates whether call should be - * blocking or not. */ + ConsoleInfo *infoPtr, /* Console state. */ + int blocking) /* Indicates whether call should be blocking + * or not. */ { DWORD timeout, count; HANDLE *handle = infoPtr->handle; INPUT_RECORD input; - + while (1) { /* * Synchronize with the reader thread. */ - + timeout = blocking ? INFINITE : 0; if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { /* * The reader thread is blocked waiting for data and the channel * is in non-blocking mode. */ + errno = EAGAIN; return -1; } - + /* - * At this point, the two threads are synchronized, so it is safe - * to access shared state. + * At this point, the two threads are synchronized, so it is safe to + * access shared state. */ - + /* * If the console has hit EOF, it is always readable. */ - + if (infoPtr->readFlags & CONSOLE_EOF) { return 1; } - + if (PeekConsoleInput(handle, &input, 1, &count) == FALSE) { - /* + /* * Check to see if the peek failed because of EOF. */ - + TclWinConvertError(GetLastError()); - + if (errno == EOF) { infoPtr->readFlags |= CONSOLE_EOF; return 1; @@ -1008,7 +1092,7 @@ WaitForRead( /* * Ignore errors if there is data in the buffer. */ - + if (infoPtr->readFlags & CONSOLE_BUFFERED) { return 0; } else { @@ -1017,20 +1101,18 @@ WaitForRead( } /* - * If there is data in the buffer, the console must be - * readable (since it is a line-oriented device). + * If there is data in the buffer, the console must be readable (since + * it is a line-oriented device). */ if (infoPtr->readFlags & CONSOLE_BUFFERED) { return 1; } - /* - * There wasn't any data available, so reset the thread and - * try again. + * There wasn't any data available, so reset the thread and try again. */ - + ResetEvent(infoPtr->readable); SetEvent(infoPtr->startReader); } @@ -1041,74 +1123,95 @@ WaitForRead( * * ConsoleReaderThread -- * - * This function runs in a separate thread and waits for input - * to become available on a console. + * This function runs in a separate thread and waits for input to become + * available on a console. * * Results: * None. * * Side effects: - * Signals the main thread when input become available. May - * cause the main thread to wake up by posting a message. May - * one line from the console for each wait operation. + * Signals the main thread when input become available. May cause the + * main thread to wake up by posting a message. May one line from the + * console for each wait operation. * *---------------------------------------------------------------------- */ static DWORD WINAPI -ConsoleReaderThread(LPVOID arg) +ConsoleReaderThread( + LPVOID arg) { ConsoleInfo *infoPtr = (ConsoleInfo *)arg; HANDLE *handle = infoPtr->handle; - DWORD count; + DWORD 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); - count = 0; + 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; + } - /* - * Look for data on the console, but first ignore any events - * that are not KEY_EVENTs + /* + * Look for data on the console, but first ignore any events that are + * not KEY_EVENTs. */ - if (ReadConsole(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, - (LPDWORD) &infoPtr->bytesRead, NULL) != FALSE) { + + if (readConsoleBytes(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE, + (LPDWORD) &infoPtr->bytesRead) != FALSE) { /* * Data was stored in the buffer. */ - + infoPtr->readFlags |= CONSOLE_BUFFERED; } else { DWORD err; err = GetLastError(); - - if (err == EOF) { + + if (err == (DWORD)EOF) { infoPtr->readFlags = CONSOLE_EOF; } } /* - * Signal the main thread by signalling the readable event and - * then waking up the notifier thread. + * Signal the main thread by signalling the readable event and then + * waking up the notifier thread. */ SetEvent(infoPtr->readable); /* - * Alert the foreground thread. Note that we need to treat this like - * a critical section so the foreground thread does not terminate - * this thread while we are holding a mutex in the notifier code. + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. */ Tcl_MutexLock(&consoleMutex); - Tcl_ThreadAlert(infoPtr->threadId); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + Tcl_ThreadAlert(infoPtr->threadId); + } Tcl_MutexUnlock(&consoleMutex); } - return 0; /* NOT REACHED */ + + return 0; } /* @@ -1116,34 +1219,50 @@ ConsoleReaderThread(LPVOID arg) * * ConsoleWriterThread -- * - * This function runs in a separate thread and writes data - * onto a console. + * This function runs in a separate thread and writes data onto a + * console. * * Results: * Always returns 0. * * Side effects: - * Signals the main thread when an output operation is completed. - * May cause the main thread to wake up by posting a message. + + * Signals the main thread when an output operation is completed. May + * cause the main thread to wake up by posting a message. * *---------------------------------------------------------------------- */ static DWORD WINAPI -ConsoleWriterThread(LPVOID arg) +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; @@ -1153,7 +1272,8 @@ ConsoleWriterThread(LPVOID arg) */ while (toWrite > 0) { - if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) { + if (writeConsoleBytes(handle, buf, (DWORD)toWrite, + &count) == FALSE) { infoPtr->writeError = GetLastError(); break; } else { @@ -1163,26 +1283,32 @@ ConsoleWriterThread(LPVOID arg) } /* - * Signal the main thread by signalling the writable event and - * then waking up the notifier thread. + * Signal the main thread by signalling the writable event and then + * waking up the notifier thread. */ - + SetEvent(infoPtr->writable); /* - * Alert the foreground thread. Note that we need to treat this like - * a critical section so the foreground thread does not terminate - * this thread while we are holding a mutex in the notifier code. + * Alert the foreground thread. Note that we need to treat this like a + * critical section so the foreground thread does not terminate this + * thread while we are holding a mutex in the notifier code. */ Tcl_MutexLock(&consoleMutex); - Tcl_ThreadAlert(infoPtr->threadId); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + + Tcl_ThreadAlert(infoPtr->threadId); + } Tcl_MutexUnlock(&consoleMutex); } - return 0; /* NOT REACHED */ -} - + return 0; +} /* *---------------------------------------------------------------------- @@ -1190,8 +1316,8 @@ ConsoleWriterThread(LPVOID arg) * TclWinOpenConsoleChannel -- * * Constructs a Console channel for the specified standard OS handle. - * This is a helper function to break up the construction of - * channels into File, Console, or Serial. + * This is a helper function to break up the construction of channels + * into File, Console, or Serial. * * Results: * Returns the new channel, or NULL. @@ -1203,66 +1329,140 @@ ConsoleWriterThread(LPVOID arg) */ Tcl_Channel -TclWinOpenConsoleChannel(handle, channelName, permissions) - HANDLE handle; - char *channelName; - int permissions; +TclWinOpenConsoleChannel( + HANDLE handle, + char *channelName, + int permissions) { char encoding[4 + TCL_INTEGER_SPACE]; ConsoleInfo *infoPtr; - ThreadSpecificData *tsdPtr; - DWORD id; + DWORD id, modes; - tsdPtr = ConsoleInit(); + ConsoleInit(); /* * See if a channel with this handle already exists. */ - + infoPtr = (ConsoleInfo *) ckalloc((unsigned) sizeof(ConsoleInfo)); memset(infoPtr, 0, sizeof(ConsoleInfo)); infoPtr->validMask = permissions; infoPtr->handle = handle; + infoPtr->channel = (Tcl_Channel) NULL; wsprintfA(encoding, "cp%d", GetConsoleCP()); - + + infoPtr->threadId = Tcl_GetCurrentThread(); + /* - * Use the pointer for the name of the result channel. - * This keeps the channel names unique, since some may share - * handles (stdin/stdout/stderr for instance). + * Use the pointer for the name of the result channel. This keeps the + * channel names unique, since some may share handles (stdin/stdout/stderr + * for instance). */ - wsprintfA(channelName, "file%lx", (int) infoPtr); - - infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, - (ClientData) infoPtr, permissions); + sprintf(channelName, "file%" TCL_I_MODIFIER "x", (size_t)infoPtr); - infoPtr->threadId = Tcl_GetCurrentThread(); + infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName, + (ClientData) infoPtr, permissions); 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, 0, &id); - SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + infoPtr->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread, + infoPtr, 0, &id); + 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, 0, &id); + infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST); } /* - * Files have default translation of AUTO and ^Z eof char, which - * means that a ^Z will be accepted as EOF when reading. + * Files have default translation of AUTO and ^Z eof char, which means + * that a ^Z will be accepted as EOF when reading. */ - + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); - Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding); + if (tclWinProcs->useWide) + Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", "unicode"); + else + Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding); return infoPtr->channel; } + +/* + *---------------------------------------------------------------------- + * + * ConsoleThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +ConsoleThreadActionProc( + ClientData instanceData, + int action) +{ + ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData; + + /* We do not access firstConsolePtr in the thread structures. This is not + * for all serials managed by the thread, but only those we are watching. + * Removal of the filevent handlers before transfer thus takes care of + * this structure. + */ + + Tcl_MutexLock(&consoleMutex); + if (action == TCL_CHANNEL_THREAD_INSERT) { + /* + * We can't copy the thread information from the channel when the + * channel is created. At this time the channel back pointer has not + * been set yet. However in that case the threadId has already been + * set by TclpCreateCommandChannel itself, so the structure is still + * good. + */ + + ConsoleInit(); + if (infoPtr->channel != NULL) { + infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); + } + } else { + infoPtr->threadId = NULL; + } + Tcl_MutexUnlock(&consoleMutex); +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |
