summaryrefslogtreecommitdiffstats
path: root/win/tclWinConsole.c
diff options
context:
space:
mode:
Diffstat (limited to 'win/tclWinConsole.c')
-rw-r--r--win/tclWinConsole.c2511
1 files changed, 763 insertions, 1748 deletions
diff --git a/win/tclWinConsole.c b/win/tclWinConsole.c
index c7e12ae..361fb3d 100644
--- a/win/tclWinConsole.c
+++ b/win/tclWinConsole.c
@@ -2,208 +2,128 @@
* tclWinConsole.c --
*
* This file implements the Windows-specific console functions, and the
- * "console" channel driver. Windows 7 or later required.
+ * "console" channel driver.
*
- * Copyright © 2022 Ashok P. Nadkarni
+ * 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.
*/
-#ifdef TCL_CONSOLE_DEBUG
-#undef NDEBUG /* Enable asserts */
-#endif
-
#include "tclWinInt.h"
-#include <assert.h>
-#include <ctype.h>
+
+#include <fcntl.h>
+#include <io.h>
/*
- * A general note on the design: The console channel driver differs from
- * most other drivers in the following respects:
- *
- * - There can be at most 3 console handles at any time since Windows does
- * support allocation of more than one console (with three handles
- * corresponding to stdin, stdout, stderr)
- *
- * - Consoles are created / inherited at process startup. There is currently
- * no way in Tcl to programmatically create a console. Even if these were
- * added the above Windows limitation would still apply.
- *
- * - Unlike files, sockets etc. where there is a one-to-one
- * correspondence between Tcl channels and operating system handles,
- * std* channels are shared amongst threads which means there can be
- * multiple Tcl channels corresponding to a single console handle.
- *
- * - Even with multiple threads, more than one file event handler is
- * unlikely. It does not make sense for multiple threads to register
- * handlers for stdin because the input would be randomly fragmented amongst
- * the threads.
- *
- * Various design factors are driven by the above, e.g. use of lists instead
- * of hash tables (at most 3 console handles) and use of global instead of
- * per thread queues which simplifies lock management particularly because
- * thread-console relation is not one-one and is likely more performant as
- * well with fewer locks needing to be obtained.
- *
- * Some additional design notes/reminders for the future:
- *
- * Aligned, synchronous reads are done directly by interpreter thread.
- * Unaligned or asynchronous reads are done through the reader thread.
- *
- * The reader thread does not read ahead. That is, it will not post a read
- * until some interpreter thread is actually requesting a read. This is
- * because an interpreter may (for example) turn off echo for passwords and
- * the read ahead would come in the way of that.
- *
- * If multiple threads are reading from stdin, the input is sprayed in
- * random fashion. This is not good application design and hence no plan to
- * address this (not clear what should be done even in theory)
- *
- * For output, we do not restrict all output to the console writer threads.
- * See ConsoleOutputProc for the conditions.
- *
- * Locks are never held when calling the ReadConsole/WriteConsole API's
- * since they may block.
+ * The following variable is used to tell whether this module has been
+ * initialized.
*/
-static int gInitialized = 0;
+static int initialized = 0;
/*
- * INPUT_BUFFER_SIZE is size of buffer passed to ReadConsole in bytes.
- * Note that ReadConsole will only allow reading of line lengths up to the
- * max of 256 and buffer size passed to it. So dropping this below 512
- * means user can type at most 256 chars.
+ * The consoleMutex locks around access to the initialized variable, and it is
+ * used to protect background threads from being terminated while they are
+ * using APIs that hold locks.
*/
-#ifndef INPUT_BUFFER_SIZE
-#define INPUT_BUFFER_SIZE 8192 /* In bytes, so 4096 chars */
-#endif
-/*
- * CONSOLE_BUFFER_SIZE is size of storage used in ring buffers.
- * In theory, at least sizeof(WCHAR) but note the Tcl channel bug
- * https://core.tcl-lang.org/tcl/tktview/b3977d199b08e3979a8da970553d5209b3042e9c
- * will cause failures in test suite if close to max input line in the suite.
- */
-#ifndef CONSOLE_BUFFER_SIZE
-#define CONSOLE_BUFFER_SIZE 8192 /* In bytes */
-#endif
+TCL_DECLARE_MUTEX(consoleMutex)
/*
- * Ring buffer for storing data. Actual data is from bufPtr[start]:bufPtr[size-1]
- * and bufPtr[0]:bufPtr[length - (size-start)].
+ * Bit masks used in the flags field of the ConsoleInfo structure below.
*/
-typedef struct RingBuffer {
- char *bufPtr; /* Pointer to buffer storage */
- Tcl_Size capacity; /* Size of the buffer in RingBufferChar */
- Tcl_Size start; /* Start of the data within the buffer. */
- Tcl_Size length; /* Number of RingBufferChar*/
-} RingBuffer;
-#define RingBufferLength(ringPtr_) ((ringPtr_)->length)
-#define RingBufferHasFreeSpace(ringPtr_) ((ringPtr_)->length < (ringPtr_)->capacity)
-#define RINGBUFFER_ASSERT(ringPtr_) assert(RingBufferCheck(ringPtr_))
+
+#define CONSOLE_PENDING (1<<0) /* Message is pending in the queue. */
+#define CONSOLE_ASYNC (1<<1) /* Channel is non-blocking. */
/*
- * The Win32 console API does not support non-blocking I/O in any form. Thus
- * the actual calls are made on a separate thread. Moreover, separate
- * threads are needed for each handle because (for example) blocking on user
- * input on stdin should not prevent output to stdout when non-blocking i/o
- * is configured at the script level.
- *
- * In the input (e.g. stdin) case, the console stdin thread is the producer
- * writing to the buffer ring buffer. The Tcl interpreter threads are the
- * consumer. For the output (e.g. stdout/stderr) case, the Tcl interpreter
- * are the producers while the console stdout/stderr threads are the
- * consumers.
- *
- * Consoles are identified purely by handles and multiple threads may open
- * them (as stdin/stdout/stderr are shared).
- *
- * Note on reference counting - a ConsoleHandleInfo instance has multiple
- * references to it - one each from every channel that is attached to it
- * plus one from the console thread itself which also serves as the reference
- * from gConsoleHandleInfoList.
+ * Bit masks used in the sharedFlags field of the ConsoleInfo structure below.
*/
-typedef struct ConsoleHandleInfo {
- struct ConsoleHandleInfo *nextPtr; /* Process-global list of consoles */
- HANDLE console; /* Console handle */
- HANDLE consoleThread; /* Handle to thread doing actual i/o on the console */
- SRWLOCK lock; /* Controls access to this structure.
- * Cheaper than CRITICAL_SECTION but note does not
- * support recursive locks or Try* style attempts.*/
- CONDITION_VARIABLE consoleThreadCV;/* For awakening console thread */
- CONDITION_VARIABLE interpThreadCV; /* For awakening interpthread(s) */
- RingBuffer buffer; /* Buffer for data transferred between console
- * threads and Tcl threads. For input consoles,
- * written by the console thread and read by Tcl
- * threads. The converse for output threads */
- DWORD initMode; /* Initial console mode. */
- DWORD lastError; /* An error caused by the last background
- * operation. Set to 0 if no error has been
- * detected. */
- int numRefs; /* See comments above */
- int permissions; /* TCL_READABLE for input consoles, TCL_WRITABLE
- * for output. Only one or the other can be set. */
- int flags;
-#define CONSOLE_DATA_AWAITED 0x0001 /* An interpreter is awaiting data */
-} ConsoleHandleInfo;
+
+#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_BUFFER_SIZE (8*1024)
/*
* This structure describes per-instance data for a console based channel.
- *
- * Note on locking - this structure has no locks because it is accessed
- * only from the thread owning channel EXCEPT when a console traverses it
- * looking for a channel that is watching for events on the console. Even
- * in that case, no locking is required because that access is only under
- * the gConsoleLock lock which prevents the channel from being removed from
- * the gWatchingChannelList which in turn means it will not be deallocated
- * from under the console thread. Access to individual fields does not need
- * to be controlled because
- * - the console thread does not write to any fields
- * - changes to the nextWatchingChannelPtr field
- * - changes to other fields do not matter because after being read for
- * queueing events, they are verified again when the event is received
- * in the interpreter thread (since they could have changed anyways while
- * the event was in-flight on the event queue)
- *
- * Note on reference counting - a structure instance may be referenced from
- * three places:
- * - the Tcl channel subsystem. This reference is created when on channel
- * opening and dropped on channel close. This also covers the reference
- * from gWatchingChannelList since queueing / dequeuing from that list
- * happens in conjunction with channel operations.
- * - the Tcl event queue entries. This reference is added when the event
- * is queued and dropped on receipt.
*/
-typedef struct ConsoleChannelInfo {
- HANDLE handle; /* Console handle */
- Tcl_ThreadId threadId; /* Id of owning thread */
- struct ConsoleChannelInfo *nextWatchingChannelPtr;
- /* Pointer to next channel watching events. */
+
+typedef struct ConsoleInfo {
+ HANDLE handle;
+ int type;
+ struct ConsoleInfo *nextPtr;/* Pointer to next registered console. */
Tcl_Channel channel; /* Pointer to channel structure. */
- DWORD initMode; /* Initial console mode. */
- int numRefs; /* See comments above */
- int permissions; /* OR'ed combination of TCL_READABLE,
+ int validMask; /* OR'ed combination of TCL_READABLE,
* TCL_WRITABLE, or TCL_EXCEPTION: indicates
* which operations are valid on the file. */
int watchMask; /* OR'ed combination of TCL_READABLE,
* TCL_WRITABLE, or TCL_EXCEPTION: indicates
* which events should be reported. */
- int flags; /* State flags */
-#define CONSOLE_EVENT_QUEUED 0x0001 /* Notification event already queued */
-#define CONSOLE_ASYNC 0x0002 /* Channel is non-blocking. */
-#define CONSOLE_READ_OPS 0x0004 /* Channel supports read-related ops. */
-} ConsoleChannelInfo;
+ int flags; /* State flags, see above for a list. */
+ Tcl_ThreadId threadId; /* Thread to which events should be reported.
+ * This value is used by the reader/writer
+ * threads. */
+ 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. */
+ 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. */
+ 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
+ * 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. */
+ 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 */
+ char buffer[CONSOLE_BUFFER_SIZE];
+ /* 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.
+ */
+
+ ConsoleInfo *firstConsolePtr;
+} ThreadSpecificData;
+
+static Tcl_ThreadDataKey dataKey;
/*
* The following structure is what is added to the Tcl event queue when
* console events are generated.
*/
-typedef struct {
- Tcl_Event header; /* Information that is standard for all events. */
- ConsoleChannelInfo *chanInfoPtr;
- /* Pointer to console info structure. Note
+typedef struct ConsoleEvent {
+ 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. */
@@ -213,408 +133,94 @@ typedef struct {
* Declarations for functions used only in this file.
*/
-static int ConsoleBlockModeProc(void *instanceData, int mode);
-static void ConsoleCheckProc(void *clientData, int flags);
-static int ConsoleCloseProc(void *instanceData,
- Tcl_Interp *interp, int flags);
+static int ConsoleBlockModeProc(ClientData instanceData,int mode);
+static void ConsoleCheckProc(ClientData clientData, int flags);
+static int ConsoleCloseProc(ClientData instanceData,
+ Tcl_Interp *interp);
static int ConsoleEventProc(Tcl_Event *evPtr, int flags);
-static void ConsoleExitHandler(void *clientData);
-static int ConsoleGetHandleProc(void *instanceData,
- int direction, void **handlePtr);
-static int ConsoleGetOptionProc(void *instanceData,
- Tcl_Interp *interp, const char *optionName,
- Tcl_DString *dsPtr);
+static void ConsoleExitHandler(ClientData clientData);
+static int ConsoleGetHandleProc(ClientData instanceData,
+ int direction, ClientData *handlePtr);
static void ConsoleInit(void);
-static int ConsoleInputProc(void *instanceData, char *buf,
+static int ConsoleInputProc(ClientData instanceData, char *buf,
int toRead, int *errorCode);
-static int ConsoleOutputProc(void *instanceData,
- const char *buf, int toWrite, int *errorCode);
-static int ConsoleSetOptionProc(void *instanceData,
- Tcl_Interp *interp, const char *optionName,
- const char *value);
-static void ConsoleSetupProc(void *clientData, int flags);
-static void ConsoleWatchProc(void *instanceData, int mask);
-static void ProcExitHandler(void *clientData);
-static void ConsoleThreadActionProc(void *instanceData, int action);
-static DWORD ReadConsoleChars(HANDLE hConsole, WCHAR *lpBuffer,
- Tcl_Size nChars, Tcl_Size *nCharsReadPtr);
-static DWORD WriteConsoleChars(HANDLE hConsole,
- const WCHAR *lpBuffer, Tcl_Size nChars,
- Tcl_Size *nCharsWritten);
-static void RingBufferInit(RingBuffer *ringPtr, Tcl_Size capacity);
-static void RingBufferClear(RingBuffer *ringPtr);
-static Tcl_Size RingBufferIn(RingBuffer *ringPtr, const char *srcPtr,
- Tcl_Size srcLen, int partialCopyOk);
-static Tcl_Size RingBufferOut(RingBuffer *ringPtr, char *dstPtr,
- Tcl_Size dstCapacity, int partialCopyOk);
-static ConsoleHandleInfo *AllocateConsoleHandleInfo(HANDLE consoleHandle,
- int permissions);
-static ConsoleHandleInfo *FindConsoleInfo(const ConsoleChannelInfo *);
+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 NudgeWatchers(HANDLE consoleHandle);
-#ifndef NDEBUG
-static int RingBufferCheck(const RingBuffer *ringPtr);
-#endif
-
-/*
- * Static data.
- */
-
-typedef struct {
- /* Currently this struct is only used to detect thread initialization */
- int notUsed; /* Dummy field */
-} ThreadSpecificData;
-static Tcl_ThreadDataKey dataKey;
-
-/*
- * All access to static data is controlled through a single process-wide
- * lock. A process can have only a single console at a time, with three
- * handles for stdin, stdout and stderr. Creation/destruction of consoles is
- * a relatively rare event (currently only possible during process start),
- * the number of consoles (as opposed to channels) is small (only stdin,
- * stdout and stderr), and contention low. More finer-grained locking would
- * likely not only complicate implementation but be slower due to multiple
- * locks being held. Note console channels also differ from other Tcl
- * channel types in that the channel<->OS descriptor mapping is not one-to-one.
- */
-SRWLOCK gConsoleLock;
-
-
-/* Process-wide list of console handles. Access control through gConsoleLock */
-static ConsoleHandleInfo *gConsoleHandleInfoList;
-
-/*
- * Process-wide list of channels that are listening for events. Again access
- * control through gConsoleLock. Common list for all threads is simplifies
- * locking and bookkeeping and is workable because in practice multiple
- * threads are very unlikely to be all waiting on stdin (not workable
- * because input would be randomly distributed to threads)
- */
-static ConsoleChannelInfo *gWatchingChannelList;
+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
* based IO.
*/
-static const Tcl_ChannelType consoleChannelType = {
- "console", /* Type name. */
- TCL_CHANNEL_VERSION_5, /* v5 channel */
- TCL_CLOSE2PROC, /* Close proc. */
- ConsoleInputProc, /* Input proc. */
- ConsoleOutputProc, /* Output proc. */
- NULL, /* Seek proc. */
- ConsoleSetOptionProc, /* Set option proc. */
- ConsoleGetOptionProc, /* Get option proc. */
- ConsoleWatchProc, /* Set up notifier to watch the channel. */
- ConsoleGetHandleProc, /* Get an OS handle from channel. */
- ConsoleCloseProc, /* close2proc. */
- ConsoleBlockModeProc, /* Set blocking or non-blocking mode. */
- NULL, /* Flush proc. */
- NULL, /* Handler proc. */
- NULL, /* Wide seek proc. */
- ConsoleThreadActionProc, /* Thread action proc. */
- NULL /* Truncation proc. */
+static Tcl_ChannelType consoleChannelType = {
+ "console", /* Type name. */
+ TCL_CHANNEL_VERSION_5, /* v5 channel */
+ ConsoleCloseProc, /* Close proc. */
+ ConsoleInputProc, /* Input proc. */
+ ConsoleOutputProc, /* Output proc. */
+ NULL, /* Seek proc. */
+ NULL, /* Set option proc. */
+ NULL, /* Get option proc. */
+ ConsoleWatchProc, /* Set up notifier to watch the channel. */
+ ConsoleGetHandleProc, /* Get an OS handle from channel. */
+ NULL, /* close2proc. */
+ ConsoleBlockModeProc, /* Set blocking or non-blocking mode.*/
+ NULL, /* flush proc. */
+ NULL, /* handler proc. */
+ NULL, /* wide seek proc */
+ ConsoleThreadActionProc, /* thread action proc */
+ NULL, /* truncation */
};
-
-/*
- *------------------------------------------------------------------------
- *
- * RingBufferInit --
- *
- * Initializes the ring buffer to a given size.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Panics on allocation failure.
- *
- *------------------------------------------------------------------------
- */
-static void
-RingBufferInit(
- RingBuffer *ringPtr,
- Tcl_Size capacity)
-{
- if (capacity <= 0 || capacity > TCL_SIZE_MAX) {
- Tcl_Panic("Internal error: invalid ring buffer capacity requested.");
- }
- ringPtr->bufPtr = (char *) ckalloc(capacity);
- ringPtr->capacity = capacity;
- ringPtr->start = 0;
- ringPtr->length = 0;
-}
-
-/*
- *------------------------------------------------------------------------
- *
- * RingBufferClear
- *
- * Clears the contents of a ring buffer.
- *
- * Results:
- * None.
- *
- * Side effects:
- * The allocated internal buffer is freed.
- *
- *------------------------------------------------------------------------
- */
-static void
-RingBufferClear(
- RingBuffer *ringPtr)
-{
- if (ringPtr->bufPtr) {
- ckfree(ringPtr->bufPtr);
- ringPtr->bufPtr = NULL;
- }
- ringPtr->capacity = 0;
- ringPtr->start = 0;
- ringPtr->length = 0;
-}
/*
- *------------------------------------------------------------------------
- *
- * RingBufferIn --
- *
- * Appends data to the ring buffer.
- *
- * Results:
- * Returns number of bytes copied.
- *
- * Side effects:
- * Internal buffer is updated.
- *
- *------------------------------------------------------------------------
- */
-static Tcl_Size
-RingBufferIn(
- RingBuffer *ringPtr,
- const char *srcPtr, /* Source to be copied */
- Tcl_Size srcLen, /* Length of source */
- int partialCopyOk) /* If true, partial copy is permitted */
-{
- Tcl_Size freeSpace;
-
- RINGBUFFER_ASSERT(ringPtr);
-
- freeSpace = ringPtr->capacity - ringPtr->length;
- if (freeSpace < srcLen) {
- if (!partialCopyOk) {
- return 0;
- }
- /* Copy only as much as free space allows */
- srcLen = freeSpace;
- }
-
- if (ringPtr->capacity - ringPtr->start > ringPtr->length) {
- /* There is room at the back */
- Tcl_Size endSpaceStart = ringPtr->start + ringPtr->length;
- Tcl_Size endSpace = ringPtr->capacity - endSpaceStart;
- if (endSpace >= srcLen) {
- /* Everything fits at the back */
- memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, srcLen);
- } else {
- /* srcLen > endSpace */
- memmove(endSpaceStart + ringPtr->bufPtr, srcPtr, endSpace);
- memmove(ringPtr->bufPtr, endSpace + srcPtr, srcLen - endSpace);
- }
- } else {
- /* No room at the back. Existing data wrap to front. */
- Tcl_Size wrapLen =
- ringPtr->start + ringPtr->length - ringPtr->capacity;
- memmove(wrapLen + ringPtr->bufPtr, srcPtr, srcLen);
- }
-
- ringPtr->length += srcLen;
-
- RINGBUFFER_ASSERT(ringPtr);
-
- return srcLen;
-}
-
-/*
- *------------------------------------------------------------------------
- *
- * RingBufferOut --
- *
- * Moves data out of the ring buffer. If dstPtr is NULL, the data
- * is simply removed.
- *
- * Results:
- * Returns number of bytes copied or removed.
- *
- * Side effects:
- * Internal buffer is updated.
- *
- *------------------------------------------------------------------------
- */
-static Tcl_Size
-RingBufferOut(
- RingBuffer *ringPtr,
- char *dstPtr, /* Buffer for output data. May be NULL */
- Tcl_Size dstCapacity, /* Size of buffer */
- int partialCopyOk) /* If true, return what's available */
-{
- Tcl_Size leadLen;
-
- RINGBUFFER_ASSERT(ringPtr);
-
- if (dstCapacity > ringPtr->length) {
- if (dstPtr && !partialCopyOk) {
- return 0;
- }
- dstCapacity = ringPtr->length;
- }
-
- if (ringPtr->start <= (ringPtr->capacity - ringPtr->length)) {
- /* No content wrap around. So leadLen is entire content */
- leadLen = ringPtr->length;
- } else {
- /* Content wraps around so lead segment stretches to end of buffer */
- leadLen = ringPtr->capacity - ringPtr->start;
- }
- if (leadLen >= dstCapacity) {
- if (dstPtr) {
- memmove(dstPtr, ringPtr->start + ringPtr->bufPtr, dstCapacity);
- }
- ringPtr->start += dstCapacity;
- } else {
- Tcl_Size wrapLen = dstCapacity - leadLen;
- if (dstPtr) {
- memmove(dstPtr,
- ringPtr->start + ringPtr->bufPtr,
- leadLen);
- memmove(
- leadLen + dstPtr, ringPtr->bufPtr, wrapLen);
- }
- ringPtr->start = wrapLen;
- }
-
- ringPtr->length -= dstCapacity;
- if (ringPtr->start == ringPtr->capacity || ringPtr->length == 0) {
- ringPtr->start = 0;
- }
-
- RINGBUFFER_ASSERT(ringPtr);
-
- return dstCapacity;
-}
-
-#ifndef NDEBUG
-static int
-RingBufferCheck(
- const RingBuffer *ringPtr)
-{
- return (ringPtr->bufPtr != NULL && ringPtr->capacity == CONSOLE_BUFFER_SIZE
- && ringPtr->start < ringPtr->capacity
- && ringPtr->length <= ringPtr->capacity);
-}
-#endif
-
-/*
- *------------------------------------------------------------------------
- *
- * ReadConsoleChars --
- *
- * Wrapper for ReadConsoleW.
- *
- * Results:
- * Returns 0 on success, else Windows error code.
- *
- * Side effects:
- * On success the number of characters (not bytes) read is stored in
- * *nCharsReadPtr. This will be 0 if the operation was interrupted by
- * a Ctrl-C or a CancelIo call.
+ *----------------------------------------------------------------------
*
- *------------------------------------------------------------------------
+ * readConsoleBytes, writeConsoleBytes --
+ * Wrapper for ReadConsole{A,W}, that takes and returns number of bytes
+ * instead of number of TCHARS
*/
-static DWORD
-ReadConsoleChars(
+static BOOL
+readConsoleBytes(
HANDLE hConsole,
- WCHAR *lpBuffer,
- Tcl_Size nChars,
- Tcl_Size *nCharsReadPtr)
+ LPVOID lpBuffer,
+ DWORD nbytes,
+ LPDWORD nbytesread)
{
- DWORD nRead;
+ DWORD ntchars;
BOOL result;
-
- /*
- * If user types a Ctrl-Break or Ctrl-C, ReadConsole will return success
- * with ntchars == 0 and GetLastError() will be ERROR_OPERATION_ABORTED.
- * If no Ctrl signal handlers have been established, the default signal
- * OS handler in a separate thread will terminate the program. If a Ctrl
- * signal handler has been established (through an extension for
- * example), it will run and take whatever action it deems appropriate.
- *
- * If one thread closes its channel, it calls CancelSynchronousIo on the
- * console handle which results again in success being returned and
- * GetLastError() being ERROR_OPERATION_ABORTED but ntchars in
- * unmodified.
- *
- * In both cases above we will return success but with nbytesread as 0.
- * This allows caller to check for thread termination etc.
- *
- * See https://bugs.python.org/issue30237
- * or https://github.com/microsoft/terminal/issues/12143
- */
- nRead = (DWORD)-1;
- result = ReadConsoleW(hConsole, lpBuffer, nChars, &nRead, NULL);
- if (result) {
- if ((nRead == 0 || nRead == (DWORD)-1)
- && GetLastError() == ERROR_OPERATION_ABORTED) {
- nRead = 0;
- }
- *nCharsReadPtr = nRead;
- return 0;
- } else {
- return GetLastError();
- }
+ int tcharsize;
+ tcharsize = tclWinProcs->useWide? 2 : 1;
+ result = tclWinProcs->readConsoleProc(
+ hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL);
+ if (nbytesread)
+ *nbytesread = (ntchars*tcharsize);
+ return result;
}
-
-/*
- *------------------------------------------------------------------------
- *
- * WriteConsoleChars --
- *
- * Wrapper for WriteConsoleW.
- *
- * Results:
- * Returns 0 on success, Windows error code on failure.
- *
- * Side effects:
- * On success the number of characters (not bytes) written is stored in
- * *nCharsWrittenPtr. This will be 0 if the operation was interrupted by
- * a Ctrl-C or a CancelIo call.
- *
- *------------------------------------------------------------------------
- */
-static DWORD
-WriteConsoleChars(
+static BOOL
+writeConsoleBytes(
HANDLE hConsole,
- const WCHAR *lpBuffer,
- Tcl_Size nChars,
- Tcl_Size *nCharsWrittenPtr)
+ const VOID *lpBuffer,
+ DWORD nbytes,
+ LPDWORD nbyteswritten)
{
- DWORD nCharsWritten;
+ DWORD ntchars;
BOOL result;
-
- /* See comments in ReadConsoleChars, not sure that applies here */
- nCharsWritten = (DWORD)-1;
- result = WriteConsoleW(hConsole, lpBuffer, nChars, &nCharsWritten, NULL);
- if (result) {
- if (nCharsWritten == (DWORD) -1) {
- nCharsWritten = 0;
- }
- *nCharsWrittenPtr = nCharsWritten;
- return 0;
- } else {
- return GetLastError();
- }
+ int tcharsize;
+ tcharsize = tclWinProcs->useWide? 2 : 1;
+ result = tclWinProcs->writeConsoleProc(
+ hConsole, lpBuffer, nbytes / tcharsize, &ntchars, NULL);
+ if (nbyteswritten)
+ *nbyteswritten = (ntchars*tcharsize);
+ return result;
}
/*
@@ -636,24 +242,26 @@ WriteConsoleChars(
static void
ConsoleInit(void)
{
+ ThreadSpecificData *tsdPtr;
+
/*
* Check the initialized flag first, then check again in the mutex. This
* is a speed enhancement.
*/
- if (!gInitialized) {
- AcquireSRWLockExclusive(&gConsoleLock);
- if (!gInitialized) {
- gInitialized = 1;
+ if (!initialized) {
+ Tcl_MutexLock(&consoleMutex);
+ if (!initialized) {
+ initialized = 1;
Tcl_CreateExitHandler(ProcExitHandler, NULL);
}
- ReleaseSRWLockExclusive(&gConsoleLock);
+ Tcl_MutexUnlock(&consoleMutex);
}
- if (TclThreadDataKeyGet(&dataKey) == NULL) {
- ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
-
- tsdPtr->notUsed = 0;
+ tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey);
+ if (tsdPtr == NULL) {
+ tsdPtr = TCL_TSD_INIT(&dataKey);
+ tsdPtr->firstConsolePtr = NULL;
Tcl_CreateEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
Tcl_CreateThreadExitHandler(ConsoleExitHandler, NULL);
}
@@ -678,7 +286,7 @@ ConsoleInit(void)
static void
ConsoleExitHandler(
- TCL_UNUSED(void *))
+ ClientData clientData) /* Old window proc */
{
Tcl_DeleteEventSource(ConsoleSetupProc, ConsoleCheckProc, NULL);
}
@@ -702,50 +310,11 @@ ConsoleExitHandler(
static void
ProcExitHandler(
- TCL_UNUSED(void *))
+ ClientData clientData) /* Old window proc */
{
- AcquireSRWLockExclusive(&gConsoleLock);
- gInitialized = 0;
- ReleaseSRWLockExclusive(&gConsoleLock);
-}
-
-/*
- *------------------------------------------------------------------------
- *
- * NudgeWatchers --
- *
- * Wakes up all threads which have file event watchers on the passed
- * console handle.
- *
- * The function locks and releases gConsoleLock.
- * Caller must not be holding locks that will violate lock hierarchy.
- *
- * Results:
- * None.
- *
- * Side effects:
- * As above.
- *------------------------------------------------------------------------
- */
-static void
-NudgeWatchers(
- HANDLE consoleHandle)
-{
- ConsoleChannelInfo *chanInfoPtr;
- AcquireSRWLockShared(&gConsoleLock); /* Shared-read lock */
- for (chanInfoPtr = gWatchingChannelList; chanInfoPtr;
- chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) {
- /*
- * Notify channels interested in our handle AND that have
- * a thread attached.
- * No lock needed for chanInfoPtr. See ConsoleChannelInfo.
- */
- if (chanInfoPtr->handle == consoleHandle
- && chanInfoPtr->threadId != NULL) {
- Tcl_ThreadAlert(chanInfoPtr->threadId);
- }
- }
- ReleaseSRWLockShared(&gConsoleLock);
+ Tcl_MutexLock(&consoleMutex);
+ initialized = 0;
+ Tcl_MutexUnlock(&consoleMutex);
}
/*
@@ -754,9 +323,7 @@ NudgeWatchers(
* ConsoleSetupProc --
*
* This procedure is invoked before Tcl_DoOneEvent blocks waiting for an
- * event. It walks the channel list and if any input channel has data
- * available or output channel has space for data, sets the event loop
- * blocking time to 0 so that it will poll immediately.
+ * event.
*
* Results:
* None.
@@ -769,48 +336,36 @@ NudgeWatchers(
void
ConsoleSetupProc(
- TCL_UNUSED(void *),
+ ClientData data, /* Not used. */
int flags) /* Event flags as passed to Tcl_DoOneEvent. */
{
- ConsoleChannelInfo *chanInfoPtr;
+ ConsoleInfo *infoPtr;
Tcl_Time blockTime = { 0, 0 };
int block = 1;
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
/*
- * Walk the list of channels. See general comments for struct
- * ConsoleChannelInfo with regard to locking and field access.
+ * Look to see if any events are already pending. If they are, poll.
*/
- AcquireSRWLockShared(&gConsoleLock); /* READ lock - no data modification */
-
- for (chanInfoPtr = gWatchingChannelList; block && chanInfoPtr != NULL;
- chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) {
- ConsoleHandleInfo *handleInfoPtr;
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr != NULL) {
- AcquireSRWLockShared(&handleInfoPtr->lock);
- /* Remember at most one of READABLE, WRITABLE set */
- if (chanInfoPtr->watchMask & TCL_READABLE) {
- if (RingBufferLength(&handleInfoPtr->buffer) > 0
- || handleInfoPtr->lastError != ERROR_SUCCESS) {
- block = 0; /* Input data available */
- }
- } else if (chanInfoPtr->watchMask & TCL_WRITABLE) {
- if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) {
- /* TCL_WRITABLE */
- block = 0; /* Output space available */
- }
+
+ for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL;
+ infoPtr = infoPtr->nextPtr) {
+ if (infoPtr->watchMask & TCL_WRITABLE) {
+ if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
+ block = 0;
+ }
+ }
+ if (infoPtr->watchMask & TCL_READABLE) {
+ if (WaitForRead(infoPtr, 0) >= 0) {
+ block = 0;
}
- ReleaseSRWLockShared(&handleInfoPtr->lock);
}
}
- ReleaseSRWLockShared(&gConsoleLock);
-
if (!block) {
- /* At least one channel is readable/writable. Set block time to 0 */
Tcl_SetMaxBlockTime(&blockTime);
}
}
@@ -834,87 +389,57 @@ ConsoleSetupProc(
static void
ConsoleCheckProc(
- TCL_UNUSED(void *),
+ ClientData data, /* Not used. */
int flags) /* Event flags as passed to Tcl_DoOneEvent. */
{
- ConsoleChannelInfo *chanInfoPtr;
- Tcl_ThreadId me;
+ ConsoleInfo *infoPtr;
+ ConsoleEvent *evPtr;
int needEvent;
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
- me = Tcl_GetCurrentThread();
-
/*
- * Acquire a shared lock. Note this is ok even though we potentially
- * modify the chanInfoPtr->flags because chanInfoPtr is only modified
- * when it belongs to this thread and no other thread will write to it.
- * THe shared lock is intended to protect the global gWatchingChannelList
- * as we traverse it.
+ * Queue events for any ready consoles that don't already have events
+ * queued.
*/
- AcquireSRWLockShared(&gConsoleLock);
-
- for (chanInfoPtr = gWatchingChannelList; chanInfoPtr != NULL;
- chanInfoPtr = chanInfoPtr->nextWatchingChannelPtr) {
- ConsoleHandleInfo *handleInfoPtr;
- if (chanInfoPtr->threadId != me) {
- /* Some other thread owns the channel */
+ for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL;
+ infoPtr = infoPtr->nextPtr) {
+ if (infoPtr->flags & CONSOLE_PENDING) {
continue;
}
- if (chanInfoPtr->flags & CONSOLE_EVENT_QUEUED) {
- /* A notification event already queued. No point in another. */
- continue;
- }
-
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- /* Pointer is safe to access as we are holding gConsoleLock */
- if (handleInfoPtr == NULL) {
- /* Stale event */
- continue;
- }
+ /*
+ * Queue an event if the console is signaled for reading or writing.
+ */
needEvent = 0;
- AcquireSRWLockShared(&handleInfoPtr->lock);
- /* Rememeber channel is read or write, never both */
- if (chanInfoPtr->watchMask & TCL_READABLE) {
- if (RingBufferLength(&handleInfoPtr->buffer) > 0
- || handleInfoPtr->lastError != ERROR_SUCCESS) {
- needEvent = 1; /* Input data available or error/EOF */
+ if (infoPtr->watchMask & TCL_WRITABLE) {
+ if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
+ needEvent = 1;
}
- /*
- * TCL_READABLE watch means someone is looking out for data being
- * available, let reader thread know. Note channel need not be
- * ASYNC! (Bug [baa51423c2])
- */
- handleInfoPtr->flags |= CONSOLE_DATA_AWAITED;
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- } else if (chanInfoPtr->watchMask & TCL_WRITABLE) {
- if (RingBufferHasFreeSpace(&handleInfoPtr->buffer)) {
- needEvent = 1; /* Output space available */
+ }
+
+ if (infoPtr->watchMask & TCL_READABLE) {
+ if (WaitForRead(infoPtr, 0) >= 0) {
+ needEvent = 1;
}
}
- ReleaseSRWLockShared(&handleInfoPtr->lock);
if (needEvent) {
- ConsoleEvent *evPtr = (ConsoleEvent *)ckalloc(sizeof(ConsoleEvent));
-
- /* See note above loop why this can be accessed without locks */
- chanInfoPtr->flags |= CONSOLE_EVENT_QUEUED;
- chanInfoPtr->numRefs += 1; /* So it does not go away while event
- is in queue */
+ infoPtr->flags |= CONSOLE_PENDING;
+ evPtr = (ConsoleEvent *) ckalloc(sizeof(ConsoleEvent));
evPtr->header.proc = ConsoleEventProc;
- evPtr->chanInfoPtr = chanInfoPtr;
+ evPtr->infoPtr = infoPtr;
Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
}
}
-
- ReleaseSRWLockShared(&gConsoleLock);
}
-
+
+
/*
*----------------------------------------------------------------------
*
@@ -933,11 +458,11 @@ ConsoleCheckProc(
static int
ConsoleBlockModeProc(
- void *instanceData, /* Instance data for channel. */
+ ClientData instanceData, /* Instance data for channel. */
int mode) /* TCL_MODE_BLOCKING or
* TCL_MODE_NONBLOCKING. */
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
+ ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
/*
* Consoles on Windows can not be switched between blocking and
@@ -948,9 +473,9 @@ ConsoleBlockModeProc(
*/
if (mode == TCL_MODE_NONBLOCKING) {
- chanInfoPtr->flags |= CONSOLE_ASYNC;
+ infoPtr->flags |= CONSOLE_ASYNC;
} else {
- chanInfoPtr->flags &= ~CONSOLE_ASYNC;
+ infoPtr->flags &= ~(CONSOLE_ASYNC);
}
return 0;
}
@@ -973,102 +498,166 @@ ConsoleBlockModeProc(
static int
ConsoleCloseProc(
- void *instanceData, /* Pointer to ConsoleChannelInfo structure. */
- TCL_UNUSED(Tcl_Interp *),
- int flags)
+ ClientData instanceData, /* Pointer to ConsoleInfo structure. */
+ Tcl_Interp *interp) /* For error reporting. */
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- ConsoleHandleInfo *handleInfoPtr;
- int errorCode = 0;
- ConsoleChannelInfo **nextPtrPtr;
- int closeHandle;
-
- if ((flags & (TCL_CLOSE_READ | TCL_CLOSE_WRITE)) != 0) {
- return EINVAL;
- }
- /*
- * 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 while exiting. Note an explicit close in script will
- * still close the handle. That's historical behavior on all platforms.
- */
- if (!TclInThreadExit()
- || ( (GetStdHandle(STD_INPUT_HANDLE) != chanInfoPtr->handle)
- && (GetStdHandle(STD_OUTPUT_HANDLE) != chanInfoPtr->handle)
- && (GetStdHandle(STD_ERROR_HANDLE) != chanInfoPtr->handle))) {
- closeHandle = 1;
- } else {
- closeHandle = 0;
- }
+ ConsoleInfo *consolePtr = (ConsoleInfo *) instanceData;
+ int errorCode;
+ ConsoleInfo *infoPtr, **nextPtrPtr;
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
+ DWORD exitCode;
- AcquireSRWLockExclusive(&gConsoleLock);
+ errorCode = 0;
- /* Remove channel from watchers' list */
- for (nextPtrPtr = &gWatchingChannelList; *nextPtrPtr != NULL;
- nextPtrPtr = &(*nextPtrPtr)->nextWatchingChannelPtr) {
- if (*nextPtrPtr == (ConsoleChannelInfo *) chanInfoPtr) {
- *nextPtrPtr = (*nextPtrPtr)->nextWatchingChannelPtr;
- break;
- }
- }
+ /*
+ * 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.
+ */
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr) {
+ if (consolePtr->readThread) {
/*
- * Console thread may be blocked either waiting for console i/o
- * or waiting on the condition variable for buffer empty/full
+ * The thread may already have closed on it's own. Check it's exit
+ * code.
*/
- AcquireSRWLockShared(&handleInfoPtr->lock);
- if (closeHandle) {
- handleInfoPtr->console = INVALID_HANDLE_VALUE;
+ GetExitCodeThread(consolePtr->readThread, &exitCode);
+
+ if (exitCode == STILL_ACTIVE) {
+ /*
+ * 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);
+ }
}
- /* Break the thread out of blocking console i/o */
- handleInfoPtr->numRefs -= 1; /* Remove reference from this channel */
- if (handleInfoPtr->numRefs == 1) {
+ 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.
+ */
+
+ if (consolePtr->writeThread) {
+ if (consolePtr->toWrite) {
/*
- * Abort the i/o if no other threads are listening on it.
- * Note without this check, an input line will be skipped on
- * the cancel.
+ * We only need to wait if there is something to write. This may
+ * prevent infinite wait on exit. [python bug 216289]
*/
- CancelSynchronousIo(handleInfoPtr->consoleThread);
+
+ WaitForSingleObject(consolePtr->writable, INFINITE);
}
/*
- * Wake up the console handling thread. Note we do not explicitly
- * tell it handle is closed (below). It will find out on next access
+ * The thread may already have closed on it's own. Check it's exit
+ * code.
*/
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- ReleaseSRWLockShared(&handleInfoPtr->lock);
+ GetExitCodeThread(consolePtr->writeThread, &exitCode);
+
+ if (exitCode == STILL_ACTIVE) {
+ /*
+ * Set the stop event so that if the reader thread is blocked in
+ * ConsoleWriterThread on WaitForMultipleEvents, it will exit
+ * cleanly.
+ */
+
+ 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;
- ReleaseSRWLockExclusive(&gConsoleLock);
- chanInfoPtr->channel = NULL;
- chanInfoPtr->watchMask = 0;
- chanInfoPtr->permissions = 0;
+ /*
+ * 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 (closeHandle && chanInfoPtr->handle != INVALID_HANDLE_VALUE) {
- if (CloseHandle(chanInfoPtr->handle) == FALSE) {
- Tcl_WinConvertError(GetLastError());
+ if (!TclInThreadExit()
+ || ((GetStdHandle(STD_INPUT_HANDLE) != consolePtr->handle)
+ && (GetStdHandle(STD_OUTPUT_HANDLE) != consolePtr->handle)
+ && (GetStdHandle(STD_ERROR_HANDLE) != consolePtr->handle))) {
+ if (CloseHandle(consolePtr->handle) == FALSE) {
+ TclWinConvertError(GetLastError());
errorCode = errno;
}
- chanInfoPtr->handle = INVALID_HANDLE_VALUE;
}
+ consolePtr->watchMask &= consolePtr->validMask;
+
/*
- * Note, we can check and manipulate numRefs without a lock because
- * we have removed it from the watch queue so the console thread cannot
- * get at it.
+ * Remove the file from the list of watched files.
*/
- if (chanInfoPtr->numRefs > 1) {
- /* There may be references already on the event queue */
- chanInfoPtr->numRefs -= 1;
- } else {
- ckfree(chanInfoPtr);
+
+ for (nextPtrPtr = &(tsdPtr->firstConsolePtr), infoPtr = *nextPtrPtr;
+ infoPtr != NULL;
+ nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) {
+ if (infoPtr == (ConsoleInfo *)consolePtr) {
+ *nextPtrPtr = infoPtr->nextPtr;
+ break;
+ }
}
+ if (consolePtr->writeBuf != NULL) {
+ ckfree(consolePtr->writeBuf);
+ consolePtr->writeBuf = 0;
+ }
+ ckfree((char*) consolePtr);
return errorCode;
}
@@ -1090,143 +679,73 @@ ConsoleCloseProc(
*
*----------------------------------------------------------------------
*/
+
static int
ConsoleInputProc(
- void *instanceData, /* Console state. */
- char *bufPtr, /* Where to store data read. */
+ 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. */
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- ConsoleHandleInfo *handleInfoPtr;
- Tcl_Size numRead;
-
- if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) {
- return 0; /* EOF */
- }
+ ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
+ DWORD count, bytesRead = 0;
+ int result;
*errorCode = 0;
- AcquireSRWLockShared(&gConsoleLock);
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr == NULL) {
- /* Really shouldn't happen since channel is holding a reference */
- ReleaseSRWLockShared(&gConsoleLock);
- return 0; /* EOF */
+ /*
+ * 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;
}
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */
- while (1) {
- numRead = RingBufferOut(&handleInfoPtr->buffer, bufPtr, bufSize, 1);
- /*
- * Note: even if channel is closed or has an error, as long there is
- * buffered data, we will pass it up.
- */
- if (numRead != 0) {
- break;
- }
+ if (infoPtr->readFlags & CONSOLE_BUFFERED) {
/*
- * No data available.
- * - If an error was recorded, generate that and reset it.
- * - If EOF, indicate as much. It is up to the application to close
- * the channel.
- * - Otherwise, if non-blocking return EAGAIN or wait for more data.
+ * Data is stored in the buffer.
*/
- if (handleInfoPtr->lastError != 0) {
- if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) {
- numRead = 0; /* Treat as EOF */
- } else {
- Tcl_WinConvertError(handleInfoPtr->lastError);
- handleInfoPtr->lastError = 0;
- *errorCode = Tcl_GetErrno();
- numRead = -1;
- }
- break;
- }
- if (handleInfoPtr->console == INVALID_HANDLE_VALUE) {
- /* EOF - break with numRead == 0 */
- chanInfoPtr->handle = INVALID_HANDLE_VALUE;
- break;
- }
- /* For async, tell caller we are blocked */
- if (chanInfoPtr->flags & CONSOLE_ASYNC) {
- *errorCode = EWOULDBLOCK;
- numRead = -1;
- break;
- }
+ if (bufSize < (infoPtr->bytesRead - infoPtr->offset)) {
+ memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize);
+ bytesRead = bufSize;
+ infoPtr->offset += bufSize;
+ } else {
+ memcpy(buf, &infoPtr->buffer[infoPtr->offset], (size_t) bufSize);
+ bytesRead = infoPtr->bytesRead - infoPtr->offset;
- /*
- * Blocking read. Just get data from directly from console. There
- * is a small complication in that
- * 1. The destination buffer should be WCHAR aligned.
- * 2. We can only read even number of bytes (wide-character API).
- * 3. Caller has large enough buffer (else length of line user can
- * enter will be limited)
- * If any condition is not met, we defer to the
- * reader thread which handles these cases rather than dealing with
- * them here (which is a little trickier than it might sound.)
- *
- * TODO - not clear this block is a useful optimization. bufSize by
- * default is 4K which is < INPUT_BUFFER_SIZE and will rarely be
- * increased on stdin.
- */
- if ((1 & (size_t)bufPtr) == 0 /* aligned buffer */
- && (1 & bufSize) == 0 /* Even number of bytes */
- && bufSize > INPUT_BUFFER_SIZE) {
- DWORD lastError;
- Tcl_Size numChars;
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- lastError = ReadConsoleChars(chanInfoPtr->handle,
- (WCHAR *)bufPtr, bufSize / sizeof(WCHAR), &numChars);
- /* NOTE lock released so DON'T break. Return instead */
- if (lastError != ERROR_SUCCESS) {
- Tcl_WinConvertError(lastError);
- *errorCode = Tcl_GetErrno();
- return -1;
- } else if (numChars > 0) {
- /* Successfully read something. */
- return numChars * sizeof(WCHAR);
- } else {
- /*
- * Ctrl-C/Ctrl-Brk interrupt. Loop around to retry.
- * We have to reacquire the lock. No worried about handleInfoPtr
- * having gone away since the channel holds a reference.
- */
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- continue;
- }
- }
- /*
- * Deferring blocking read to reader thread.
- * Release the lock and sleep. Note that because the channel
- * holds a reference count on handleInfoPtr, it will not
- * be deallocated while the lock is released.
- */
- handleInfoPtr->flags |= CONSOLE_DATA_AWAITED;
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV,
- &handleInfoPtr->lock, INFINITE, 0)) {
- Tcl_WinConvertError(GetLastError());
- *errorCode = Tcl_GetErrno();
- numRead = -1;
- break;
+ /*
+ * Reset the buffer
+ */
+
+ infoPtr->readFlags &= ~CONSOLE_BUFFERED;
+ infoPtr->offset = 0;
}
- /* Lock is reacquired, loop back to try again */
+ return bytesRead;
}
- /* We read data. Ask for more if either async or watching for reads */
- if ((chanInfoPtr->flags & CONSOLE_ASYNC)
- || (chanInfoPtr->watchMask & TCL_READABLE)) {
- handleInfoPtr->flags |= CONSOLE_DATA_AWAITED;
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
+ /*
+ * 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 (readConsoleBytes(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &count)
+ == TRUE) {
+ buf[count] = '\0';
+ return count;
}
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- return numRead;
+ return -1;
}
/*
@@ -1246,113 +765,79 @@ ConsoleInputProc(
*
*----------------------------------------------------------------------
*/
+
static int
ConsoleOutputProc(
- void *instanceData, /* Console state. */
- const char *buf, /* The data buffer. */
+ ClientData instanceData, /* Console state. */
+ CONST char *buf, /* The data buffer. */
int toWrite, /* How many bytes to write? */
int *errorCode) /* Where to store error code. */
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- ConsoleHandleInfo *handleInfoPtr;
- Tcl_Size numWritten;
+ 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.
+ */
- if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) {
- /* Some other thread would have *previously* closed the stdio handle */
- *errorCode = EPIPE;
- return -1;
+ errno = EAGAIN;
+ goto error;
}
- AcquireSRWLockShared(&gConsoleLock);
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr == NULL) {
- /* Really shouldn't happen since channel is holding a reference */
- *errorCode = EPIPE;
- ReleaseSRWLockShared(&gConsoleLock);
- return -1;
- }
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- ReleaseSRWLockShared(&gConsoleLock); /* AFTER acquiring handleInfoPtr->lock */
+ /*
+ * Check for a background error on the last write.
+ */
- /* Keep looping until all written. Break out for async and errors */
- numWritten = 0;
- while (1) {
- /* Check for error and closing on every loop. */
- if (handleInfoPtr->lastError != 0) {
- Tcl_WinConvertError(handleInfoPtr->lastError);
- *errorCode = Tcl_GetErrno();
- numWritten = -1;
- break;
- }
- if (handleInfoPtr->console == INVALID_HANDLE_VALUE) {
- *errorCode = EPIPE;
- chanInfoPtr->handle = INVALID_HANDLE_VALUE;
- numWritten = -1;
- break;
- }
+ if (infoPtr->writeError) {
+ TclWinConvertError(infoPtr->writeError);
+ infoPtr->writeError = 0;
+ goto error;
+ }
+ if (infoPtr->flags & CONSOLE_ASYNC) {
/*
- * We can either write directly or through the console thread's
- * ring buffer. We have to do the latter when
- * (1) the operation is async since WriteConsoleChars is always blocking
- * (2) when there is already data in the ring buffer because we don't
- * want to reorder output from within a thread
- * (3) when there are an odd number of bytes since WriteConsole
- * takes whole WCHARs
- * (4) when the pointer is not aligned on WCHAR
- * The ring buffer deals with cases (3) and (4). It would be harder
- * to duplicate that here.
+ * The console is non-blocking, so copy the data into the output
+ * buffer and restart the writer thread.
*/
- if ((chanInfoPtr->flags & CONSOLE_ASYNC) /* Case (1) */
- || RingBufferLength(&handleInfoPtr->buffer) != 0 /* Case (2) */
- || (toWrite & 1) != 0 /* Case (3) */
- || (PTR2INT(buf) & 1) != 0) { /* Case (4) */
- numWritten += RingBufferIn(&handleInfoPtr->buffer,
- numWritten + buf, toWrite - numWritten, 1);
- if (numWritten == toWrite || chanInfoPtr->flags & CONSOLE_ASYNC) {
- /* All done or async, just accept whatever was written */
- break;
- }
+
+ if (toWrite > infoPtr->writeBufLen) {
/*
- * Release the lock and sleep. Note that because the channel
- * holds a reference count on handleInfoPtr, it will not
- * be deallocated while the lock is released.
+ * Reallocate the buffer to be large enough to hold the data.
*/
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- if (!SleepConditionVariableSRW(&handleInfoPtr->interpThreadCV,
- &handleInfoPtr->lock, INFINITE, 0)) {
- /* Report the error */
- Tcl_WinConvertError(GetLastError());
- *errorCode = Tcl_GetErrno();
- numWritten = -1;
- break;
- }
- } else {
- /* Direct output */
- DWORD winStatus;
- HANDLE consoleHandle = handleInfoPtr->console;
- /* Unlock before blocking in WriteConsole */
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- /* UNLOCKED so return, DON'T break out of loop as it will unlock
- * again! */
- winStatus = WriteConsoleChars(consoleHandle,
- (WCHAR *)buf, toWrite / sizeof(WCHAR), &numWritten);
- if (winStatus == ERROR_SUCCESS) {
- return numWritten * sizeof(WCHAR);
- } else {
- Tcl_WinConvertError(winStatus);
- *errorCode = Tcl_GetErrno();
- return -1;
+
+ if (infoPtr->writeBuf) {
+ ckfree(infoPtr->writeBuf);
}
+ infoPtr->writeBufLen = toWrite;
+ infoPtr->writeBuf = ckalloc((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.
+ */
- /* Lock must have been reacquired before continuing loop */
+ if (writeConsoleBytes(infoPtr->handle, buf, (DWORD)toWrite,
+ &bytesWritten)
+ == FALSE) {
+ TclWinConvertError(GetLastError());
+ goto error;
+ }
}
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- return numWritten;
+ return bytesWritten;
+
+ error:
+ *errorCode = errno;
+ return -1;
}
/*
@@ -1382,84 +867,66 @@ ConsoleEventProc(
int flags) /* Flags that indicate what events to handle,
* such as TCL_FILE_EVENTS. */
{
- ConsoleEvent *consoleEvPtr = (ConsoleEvent *) evPtr;
- ConsoleChannelInfo *chanInfoPtr;
- int freeChannel;
- int mask = 0;
+ ConsoleEvent *consoleEvPtr = (ConsoleEvent *)evPtr;
+ ConsoleInfo *infoPtr;
+ int mask;
+ ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return 0;
}
- chanInfoPtr = consoleEvPtr->chanInfoPtr;
/*
- * We know chanInfoPtr is valid because its reference count would have
- * been incremented when the event was queued. The corresponding release
- * happens in this function.
+ * 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.
*/
+ for (infoPtr = tsdPtr->firstConsolePtr; infoPtr != NULL;
+ infoPtr = infoPtr->nextPtr) {
+ if (consoleEvPtr->infoPtr == infoPtr) {
+ infoPtr->flags &= ~(CONSOLE_PENDING);
+ break;
+ }
+ }
+
/*
- * Global lock used for chanInfoPtr. A read (shared) lock suffices
- * because all access is within the channel owning thread with the
- * exception of watchers which is a read-only access. See comments
- * to ConsoleChannelInfo.
+ * Remove stale events.
*/
- AcquireSRWLockShared(&gConsoleLock);
- chanInfoPtr->flags &= ~CONSOLE_EVENT_QUEUED;
+
+ if (!infoPtr) {
+ return 1;
+ }
/*
- * Only handle the event if the Tcl channel has not gone away AND is
- * still owned by this thread AND is still watching events.
+ * 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.
*/
- if (chanInfoPtr->channel && chanInfoPtr->threadId == Tcl_GetCurrentThread()
- && (chanInfoPtr->watchMask & (TCL_READABLE|TCL_WRITABLE))) {
- ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr == NULL) {
- /* Console was closed. EOF->read event only (not write) */
- if (chanInfoPtr->watchMask & TCL_READABLE) {
- mask = TCL_READABLE;
- }
- } else {
- AcquireSRWLockShared(&handleInfoPtr->lock);
- /* Remember at most one of READABLE, WRITABLE set */
- if ((chanInfoPtr->watchMask & TCL_READABLE)
- && RingBufferLength(&handleInfoPtr->buffer)) {
+
+ mask = 0;
+ if (infoPtr->watchMask & TCL_WRITABLE) {
+ if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) {
+ mask = TCL_WRITABLE;
+ }
+ }
+
+ if (infoPtr->watchMask & TCL_READABLE) {
+ if (WaitForRead(infoPtr, 0) >= 0) {
+ if (infoPtr->readFlags & CONSOLE_EOF) {
mask = TCL_READABLE;
- } else if ((chanInfoPtr->watchMask & TCL_WRITABLE)
- && RingBufferHasFreeSpace(&handleInfoPtr->buffer)) {
- /* Generate write event space available */
- mask = TCL_WRITABLE;
+ } else {
+ mask |= TCL_READABLE;
}
- ReleaseSRWLockShared(&handleInfoPtr->lock);
}
}
/*
- * Tcl_NotifyChannel can recurse through the file event callback so need
- * to release locks first. Our reference still holds so no danger of
- * chanInfoPtr being deallocated if the callback closes the channel.
+ * Inform the channel of the events.
*/
- ReleaseSRWLockShared(&gConsoleLock);
- if (mask) {
- Tcl_NotifyChannel(chanInfoPtr->channel, mask);
- /* Note: chanInfoPtr ref count may have changed */
- }
-
- /* No need to lock - see comments earlier */
-
- /* Remove the reference to the channel from event record */
- if (chanInfoPtr->numRefs > 1) {
- chanInfoPtr->numRefs -= 1;
- freeChannel = 0;
- } else {
- assert(chanInfoPtr->channel == NULL);
- freeChannel = 1;
- }
-
- if (freeChannel) {
- ckfree(chanInfoPtr);
- }
+ Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask);
return 1;
}
@@ -1481,57 +948,42 @@ ConsoleEventProc(
static void
ConsoleWatchProc(
- void *instanceData, /* Console state. */
- int newMask) /* What events to watch for, one of
- * of TCL_READABLE, TCL_WRITABLE */
+ ClientData instanceData, /* Console state. */
+ int mask) /* What events to watch for, OR-ed combination
+ * of TCL_READABLE, TCL_WRITABLE and
+ * TCL_EXCEPTION. */
{
- ConsoleChannelInfo **nextPtrPtr, *ptr;
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- int oldMask = chanInfoPtr->watchMask;
+ ConsoleInfo **nextPtrPtr, *ptr;
+ ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
+ int oldMask = infoPtr->watchMask;
+ 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.
*/
- chanInfoPtr->watchMask = newMask & chanInfoPtr->permissions;
- if (chanInfoPtr->watchMask) {
+ infoPtr->watchMask = mask & infoPtr->validMask;
+ if (infoPtr->watchMask) {
Tcl_Time blockTime = { 0, 0 };
-
if (!oldMask) {
- AcquireSRWLockExclusive(&gConsoleLock);
- /* Add to list of watched channels */
- chanInfoPtr->nextWatchingChannelPtr = gWatchingChannelList;
- gWatchingChannelList = chanInfoPtr;
-
- /*
- * For read channels, need to tell the console reader thread
- * that we are looking for data since it will not do reads until
- * it knows someone is awaiting.
- */
- ConsoleHandleInfo *handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr) {
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- handleInfoPtr->flags |= CONSOLE_DATA_AWAITED;
- WakeConditionVariable(&handleInfoPtr->consoleThreadCV);
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- }
- ReleaseSRWLockExclusive(&gConsoleLock);
+ infoPtr->nextPtr = tsdPtr->firstConsolePtr;
+ tsdPtr->firstConsolePtr = infoPtr;
}
Tcl_SetMaxBlockTime(&blockTime);
} else if (oldMask) {
- /* Remove from list of watched channels */
+ /*
+ * Remove the console from the list of watched consoles.
+ */
- AcquireSRWLockExclusive(&gConsoleLock);
- for (nextPtrPtr = &gWatchingChannelList, ptr = *nextPtrPtr;
+ for (nextPtrPtr = &(tsdPtr->firstConsolePtr), ptr = *nextPtrPtr;
ptr != NULL;
- nextPtrPtr = &ptr->nextWatchingChannelPtr, ptr = *nextPtrPtr) {
- if (chanInfoPtr == ptr) {
- *nextPtrPtr = ptr->nextWatchingChannelPtr;
+ nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) {
+ if (infoPtr == ptr) {
+ *nextPtrPtr = ptr->nextPtr;
break;
}
}
- ReleaseSRWLockExclusive(&gConsoleLock);
}
}
@@ -1555,70 +1007,117 @@ ConsoleWatchProc(
static int
ConsoleGetHandleProc(
- void *instanceData, /* The console state. */
- TCL_UNUSED(int) /*direction*/,
- void **handlePtr) /* Where to store the handle. */
+ ClientData instanceData, /* The console state. */
+ int direction, /* TCL_READABLE or TCL_WRITABLE */
+ ClientData *handlePtr) /* Where to store the handle. */
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
+ ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
- if (chanInfoPtr->handle == INVALID_HANDLE_VALUE) {
- return TCL_ERROR;
- } else {
- *handlePtr = chanInfoPtr->handle;
- return TCL_OK;
- }
+ *handlePtr = (ClientData) infoPtr->handle;
+ return TCL_OK;
}
/*
- *------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*
- * ConsoleDataAvailable --
+ * WaitForRead --
*
- * Checks if there is data in the console input queue.
+ * 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 the input queue has data, -1 on error else 0 if empty.
+ * 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:
- * None.
+ * Updates the shared state flags. If no error occurred, the reader
+ * thread is blocked waiting for a signal from the main thread.
*
- *------------------------------------------------------------------------
+ *----------------------------------------------------------------------
*/
- static int
- ConsoleDataAvailable(
- HANDLE consoleHandle)
+
+static int
+WaitForRead(
+ ConsoleInfo *infoPtr, /* Console state. */
+ int blocking) /* Indicates whether call should be blocking
+ * or not. */
{
- INPUT_RECORD input[10];
- DWORD count;
- DWORD i;
+ DWORD timeout, count;
+ HANDLE *handle = infoPtr->handle;
+ INPUT_RECORD input;
- /*
- * Need at least one keyboard event.
- */
- if (PeekConsoleInputW(consoleHandle, input,
- sizeof(input) / sizeof(input[0]), &count) == FALSE) {
- return -1;
- }
- /*
- * Even if windows size and mouse events are disabled, can still have
- * events other than keyboard, like focus events. Look for at least one
- * keydown event because a trailing LF keyup is always present from the
- * last input. However, if our buffer is full, assume there is a key
- * down somewhere in the unread buffer. I suppose we could expand the
- * buffer but not worth...
- */
- if (count == (sizeof(input)/sizeof(input[0]))) {
- return 1;
- }
- for (i = 0; i < count; ++i) {
- if (input[i].EventType == KEY_EVENT
- && input[i].Event.KeyEvent.bKeyDown) {
+ 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.
+ */
+
+ /*
+ * 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;
+ }
+
+ /*
+ * Ignore errors if there is data in the buffer.
+ */
+
+ if (infoPtr->readFlags & CONSOLE_BUFFERED) {
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+
+ /*
+ * 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.
+ */
+
+ ResetEvent(infoPtr->readable);
+ SetEvent(infoPtr->startReader);
}
- return 0;
}
-
+
/*
*----------------------------------------------------------------------
*
@@ -1628,10 +1127,12 @@ ConsoleGetHandleProc(
* available on a console.
*
* Results:
- * Always 0.
+ * None.
*
* Side effects:
- * Signals the main thread when input become available.
+ * 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.
*
*----------------------------------------------------------------------
*/
@@ -1640,182 +1141,75 @@ static DWORD WINAPI
ConsoleReaderThread(
LPVOID arg)
{
- ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg;
- ConsoleHandleInfo **iterator;
- Tcl_Size inputLen = 0;
- Tcl_Size inputOffset = 0;
- Tcl_Size lastReadSize = 0;
- DWORD sleepTime;
- char inputChars[INPUT_BUFFER_SIZE];
+ ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
+ HANDLE *handle = infoPtr->handle;
+ DWORD waitResult;
+ HANDLE wEvents[2];
- /*
- * Keep looping until one of the following happens.
- * - there are no more channels listening on the console
- * - the console handle has been closed
- */
+ /* The first event takes precedence. */
+ wEvents[0] = infoPtr->stopReader;
+ wEvents[1] = infoPtr->startReader;
- /* This thread is holding a reference so pointer is safe */
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
+ for (;;) {
+ /*
+ * Wait for the main thread to signal before attempting to wait.
+ */
- while (1) {
+ waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE);
- if (handleInfoPtr->numRefs == 1) {
+ if (waitResult != (WAIT_OBJECT_0 + 1)) {
/*
- * Sole reference. That's this thread. Exit since no clients
- * and no way for a thread to attach to a console after process
- * start.
+ * The start event was not signaled. It must be the stop event or
+ * an error, so exit this thread.
*/
+
break;
}
/*
- * Shared buffer has no data. If we have some in our private buffer
- * copy that. Else check if there has been an error. In both cases
- * notify the interp threads.
+ * Look for data on the console, but first ignore any events that are
+ * not KEY_EVENTs.
*/
- if (inputLen > 0 || handleInfoPtr->lastError != 0) {
- HANDLE consoleHandle;
- if (inputLen > 0) {
- /* Private buffer has data. Copy it over. */
- Tcl_Size nStored;
-
- assert((inputLen - inputOffset) > 0);
- nStored = RingBufferIn(&handleInfoPtr->buffer,
- inputOffset + inputChars, inputLen - inputOffset,
- 1);
- inputOffset += nStored;
- if (inputOffset == inputLen) {
- /* Temp buffer now empty */
- inputOffset = 0;
- inputLen = 0;
- }
- } else {
- /*
- * On error, nothing but inform caller and wait
- * We do not want to exit until there are no client interps.
- */
- }
+ if (readConsoleBytes(handle, infoPtr->buffer, CONSOLE_BUFFER_SIZE,
+ (LPDWORD) &infoPtr->bytesRead) != FALSE) {
/*
- * Wake up any threads waiting either synchronously or
- * asynchronously. Since we are providing data, turn off the
- * AWAITED flag. If the data provided is not sufficient the
- * clients will request again. Note we have to wake up ALL
- * awaiting threads, not just one, so they can all reissue
- * requests if needed. (In a properly designed app, at most one
- * thread should be reading standard input but...)
- */
- handleInfoPtr->flags &= ~CONSOLE_DATA_AWAITED;
- /* Wake synchronous channels */
- WakeAllConditionVariable(&handleInfoPtr->interpThreadCV);
- /*
- * Wake up async channels registered for file events. Note in
- * order to follow the locking hierarchy, we need to release
- * handleInfoPtr->lock before calling NudgeWatchers.
+ * Data was stored in the buffer.
*/
- consoleHandle = handleInfoPtr->console;
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- NudgeWatchers(consoleHandle);
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
-
- /*
- * Loop back to recheck for exit conditions changes while the
- * the lock was not held.
- */
- continue;
- }
- assert(inputLen == 0);
+ infoPtr->readFlags |= CONSOLE_BUFFERED;
+ } else {
+ DWORD err;
+ err = GetLastError();
- /*
- * Read more data in two cases:
- * 1. The previous read filled the buffer and there could be more
- * data in the console internal *text* buffer. Note
- * ConsolePendingInput (checked in ConsoleDataAvailable) will NOT
- * show this. It holds input events not yet translated to text.
- * 2. Tcl threads want more data AND there is data in the
- * ConsolePendingInput buffer. The latter check necessary because
- * we do not want to read ahead because the interp thread might
- * change the read mode, e.g. turning off echo for password
- * input. So only do so if at least one interpreter has requested
- * data.
- */
- if (lastReadSize == sizeof(inputChars)
- || ((handleInfoPtr->flags & CONSOLE_DATA_AWAITED)
- && ConsoleDataAvailable(handleInfoPtr->console))) {
- DWORD error;
- /* Do not hold the lock while blocked in console */
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- error = ReadConsoleChars(handleInfoPtr->console,
- (WCHAR *)inputChars, sizeof(inputChars) / sizeof(WCHAR),
- &inputLen);
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- if (error == 0) {
- inputLen *= sizeof(WCHAR);
- lastReadSize = inputLen;
- } else {
- /*
- * We only store the last error. It is up to channel
- * handlers whether to close or not in case of errors.
- */
- lastReadSize = 0;
- handleInfoPtr->lastError = error;
- if (handleInfoPtr->lastError == ERROR_INVALID_HANDLE) {
- handleInfoPtr->console = INVALID_HANDLE_VALUE;
- }
+ if (err == (DWORD)EOF) {
+ infoPtr->readFlags = CONSOLE_EOF;
}
- } else {
- /*
- * Either no one was asking for data, or no data was available.
- * In the former case, wait until someone wakes us asking for
- * data. In the latter case, there is no alternative but to
- * poll since ReadConsole does not support async operation.
- * So sleep for a short while and loop back to retry.
- */
- sleepTime =
- handleInfoPtr->flags & CONSOLE_DATA_AWAITED ? 50 : INFINITE;
- SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV,
- &handleInfoPtr->lock, sleepTime, 0);
}
- /* Loop again to check for exit or wait for readers to wake us */
- }
-
- /*
- * Exiting:
- * - remove the console from global list
- * - close the handle if still valid
- * - release the structure
- * Note there is not need to check for any watchers because we only
- * exit when there are no channels open to this console.
- */
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */
- for (iterator = &gConsoleHandleInfoList; *iterator;
- iterator = &(*iterator)->nextPtr) {
- if (*iterator == handleInfoPtr) {
- *iterator = handleInfoPtr->nextPtr;
- break;
- }
- }
- ReleaseSRWLockExclusive(&gConsoleLock);
+ /*
+ * Signal the main thread by signalling the readable event and then
+ * waking up the notifier thread.
+ */
- /* No need for relocking - no other thread should have access to it now */
- RingBufferClear(&handleInfoPtr->buffer);
+ SetEvent(infoPtr->readable);
- if (handleInfoPtr->console != INVALID_HANDLE_VALUE
- && handleInfoPtr->lastError != ERROR_INVALID_HANDLE) {
- SetConsoleMode(handleInfoPtr->console, handleInfoPtr->initMode);
/*
- * NOTE: we do not call CloseHandle(handleInfoPtr->console) here.
- * As per the GetStdHandle documentation, it need not be closed.
- * Other components may be directly using it. Note however that
- * an explicit chan close script command does close the handle
- * for all threads.
+ * 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.
*/
- }
- ckfree(handleInfoPtr);
+ Tcl_MutexLock(&consoleMutex);
+ 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;
}
@@ -1832,255 +1226,91 @@ ConsoleReaderThread(
* Always returns 0.
*
* Side effects:
- * Signals the main thread when an output operation is completed.
+
+ * 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)
{
- ConsoleHandleInfo *handleInfoPtr = (ConsoleHandleInfo *) arg;
- ConsoleHandleInfo **iterator;
- BOOL success;
- Tcl_Size numBytes;
- /*
- * This buffer size has no relation really with the size of the shared
- * buffer. Could be bigger or smaller. Make larger as multiple threads
- * could potentially be writing to it.
- */
- char buffer[2*CONSOLE_BUFFER_SIZE];
- /*
- * Keep looping until one of the following happens.
- *
- * - there are not more channels listening on the console
- * - the console handle has been closed
- *
- * On each iteration,
- * - if the channel buffer is empty, wait for some channel writer to write
- * - if there is data in our buffer, write it to the console
- */
-
- /* This thread is holding a reference so pointer is safe */
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- while (1) {
- /* handleInfoPtr->lock must be held on entry to loop */
+ ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
+ HANDLE *handle = infoPtr->handle;
+ DWORD count, toWrite, waitResult;
+ char *buf;
+ HANDLE wEvents[2];
- int offset;
- HANDLE consoleHandle;
+ /* The first event takes precedence. */
+ wEvents[0] = infoPtr->stopWriter;
+ wEvents[1] = infoPtr->startWriter;
+ for (;;) {
/*
- * Sadly, we need to do another copy because do not want to hold
- * a lock on handleInfoPtr->buffer while calling WriteConsole as that
- * might block. Also, we only want to copy an integral number of
- * WCHAR's, i.e. even number of chars so do some length checks up
- * front.
+ * Wait for the main thread to signal before attempting to write.
*/
- numBytes = RingBufferLength(&handleInfoPtr->buffer);
- numBytes &= ~1; /* Copy integral number of WCHARs -> even number of bytes */
- if (numBytes == 0) {
- /* No data to write */
- if (handleInfoPtr->numRefs == 1) {
- /*
- * Sole reference. That's this thread. Exit since no clients
- * and no buffered output.
- */
- break;
- }
- /* Wake up any threads waiting synchronously. */
- WakeConditionVariable(&handleInfoPtr->interpThreadCV);
- success = SleepConditionVariableSRW(&handleInfoPtr->consoleThreadCV,
- &handleInfoPtr->lock, INFINITE, 0);
- /* Note: lock has been acquired again! */
- if (!success && GetLastError() != ERROR_TIMEOUT) {
- /* TODO - what can be done? Should not happen */
- /* For now keep going */
- }
- continue;
- }
- /* We have data to write */
- if ((size_t)numBytes > (sizeof(buffer) / sizeof(buffer[0]))) {
- numBytes = sizeof(buffer);
+ 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;
}
- /* No need to check result, we already checked length bytes available */
- RingBufferOut(&handleInfoPtr->buffer, buffer, numBytes, 0);
-
- consoleHandle = handleInfoPtr->console;
- WakeConditionVariable(&handleInfoPtr->interpThreadCV);
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- offset = 0;
- while (numBytes > 0) {
- Tcl_Size numWChars = numBytes / sizeof(WCHAR);
- DWORD status;
- status = WriteConsoleChars(handleInfoPtr->console,
- (WCHAR *)(offset + buffer), numWChars, &numWChars);
- if (status != 0) {
- /* Only overwrite if no previous error */
- if (handleInfoPtr->lastError == 0) {
- handleInfoPtr->lastError = status;
- }
- if (status == ERROR_INVALID_HANDLE) {
- handleInfoPtr->console = INVALID_HANDLE_VALUE;
- }
- /* Assume this write is done but keep looping in case
- * it is a transient error. Not sure just closing handle
- * and exiting thread is a good idea until all references
- * from interp threads are gone.
- */
+
+ buf = infoPtr->writeBuf;
+ toWrite = infoPtr->toWrite;
+
+ /*
+ * Loop until all of the bytes are written or an error occurs.
+ */
+
+ while (toWrite > 0) {
+ if (writeConsoleBytes(handle, buf, (DWORD)toWrite,
+ &count) == FALSE) {
+ infoPtr->writeError = GetLastError();
break;
+ } else {
+ toWrite -= count;
+ buf += count;
}
- numBytes -= numWChars * sizeof(WCHAR);
- offset += numWChars * sizeof(WCHAR);
}
- /* Wake up any threads waiting synchronously. */
- WakeConditionVariable(&handleInfoPtr->interpThreadCV);
/*
- * Wake up all channels registered for file events. Note in
- * order to follow the locking hierarchy, we cannot hold any locks
- * when calling NudgeWatchers.
+ * Signal the main thread by signalling the writable event and then
+ * waking up the notifier thread.
*/
- NudgeWatchers(consoleHandle);
-
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- }
- /*
- * Exiting:
- * - remove the console from global list
- * - release the structure
- * NOTE: we do not call CloseHandle(handleInfoPtr->console) here.
- * As per the GetStdHandle documentation, it need not be closed.
- * Other components may be directly using it. Note however that
- * an explicit chan close script command does close the handle
- * for all threads.
- */
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
- AcquireSRWLockExclusive(&gConsoleLock); /* Modifying - exclusive lock */
- for (iterator = &gConsoleHandleInfoList; *iterator;
- iterator = &(*iterator)->nextPtr) {
- if (*iterator == handleInfoPtr) {
- *iterator = handleInfoPtr->nextPtr;
- break;
- }
- }
- ReleaseSRWLockExclusive(&gConsoleLock);
+ SetEvent(infoPtr->writable);
- RingBufferClear(&handleInfoPtr->buffer);
+ /*
+ * 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.
+ */
- ckfree(handleInfoPtr);
+ Tcl_MutexLock(&consoleMutex);
+ if (infoPtr->threadId != NULL) {
+ /*
+ * TIP #218. When in flight ignore the event, no one will receive
+ * it anyway.
+ */
- return 0;
-}
-
-/*
- *------------------------------------------------------------------------
- *
- * AllocateConsoleHandleInfo --
- *
- * Allocates a ConsoleHandleInfo for the passed console handle. As
- * a side effect starts a console thread to handle i/o on the handle.
- *
- * Important: Caller must be holding an EXCLUSIVE lock on gConsoleLock
- * when calling this function. The lock continues to be held on return.
- *
- * Results:
- * Pointer to an unlocked ConsoleHandleInfo structure. The reference
- * count on the structure is 1. This corresponds to the common reference
- * from the console thread and the gConsoleHandleInfoList. Returns NULL
- * on error.
- *
- * Side effects:
- * A console reader or writer thread is started. The returned structure
- * is placed on the active console handler list gConsoleHandleInfoList.
- *
- *------------------------------------------------------------------------
- */
-static ConsoleHandleInfo *
-AllocateConsoleHandleInfo(
- HANDLE consoleHandle,
- int permissions) /* TCL_READABLE or TCL_WRITABLE */
-{
- ConsoleHandleInfo *handleInfoPtr;
- DWORD consoleMode;
-
- handleInfoPtr = (ConsoleHandleInfo *) ckalloc(sizeof(*handleInfoPtr));
- memset(handleInfoPtr, 0, sizeof(*handleInfoPtr));
- memset(handleInfoPtr, 0, sizeof(*handleInfoPtr));
- handleInfoPtr->console = consoleHandle;
- InitializeSRWLock(&handleInfoPtr->lock);
- InitializeConditionVariable(&handleInfoPtr->consoleThreadCV);
- InitializeConditionVariable(&handleInfoPtr->interpThreadCV);
- RingBufferInit(&handleInfoPtr->buffer, CONSOLE_BUFFER_SIZE);
- handleInfoPtr->lastError = 0;
- handleInfoPtr->permissions = permissions;
- handleInfoPtr->numRefs = 1; /* See function header */
- if (permissions == TCL_READABLE) {
- GetConsoleMode(consoleHandle, &handleInfoPtr->initMode);
- consoleMode = handleInfoPtr->initMode;
- consoleMode &= ~(ENABLE_WINDOW_INPUT | ENABLE_MOUSE_INPUT);
- consoleMode |= ENABLE_LINE_INPUT;
- SetConsoleMode(consoleHandle, consoleMode);
- }
- handleInfoPtr->consoleThread = CreateThread(
- NULL, /* default security descriptor */
- 2*CONSOLE_BUFFER_SIZE, /* Stack size - gets rounded up to granularity */
- permissions == TCL_READABLE ? ConsoleReaderThread : ConsoleWriterThread,
- handleInfoPtr, /* Pass to thread */
- 0, /* Flags - no special cases */
- NULL); /* Don't care about thread id */
- if (handleInfoPtr->consoleThread == NULL) {
- /* Note - SRWLock and condition variables do not need finalization */
- RingBufferClear(&handleInfoPtr->buffer);
- ckfree(handleInfoPtr);
- return NULL;
+ Tcl_ThreadAlert(infoPtr->threadId);
+ }
+ Tcl_MutexUnlock(&consoleMutex);
}
- /* Chain onto global list */
- handleInfoPtr->nextPtr = gConsoleHandleInfoList;
- gConsoleHandleInfoList = handleInfoPtr;
-
- return handleInfoPtr;
+ return 0;
}
/*
- *------------------------------------------------------------------------
- *
- * FindConsoleInfo --
- *
- * Finds the ConsoleHandleInfo record for a given ConsoleChannelInfo.
- * The found record must match the console handle. It is the caller's
- * responsibility to check the permissions (read/write) in the returned
- * ConsoleHandleInfo match permissions in chanInfoPtr. This function does
- * not check that.
- *
- * Important: Caller must be holding an shared or exclusive lock on
- * gConsoleMutex. That ensures the returned pointer stays valid on
- * return without risk of deallocation by other threads.
- *
- * Results:
- * Pointer to the found ConsoleHandleInfo or NULL if not found
- *
- * Side effects:
- * None.
- *
- *------------------------------------------------------------------------
- */
-static ConsoleHandleInfo *
-FindConsoleInfo(const ConsoleChannelInfo *chanInfoPtr)
-{
- ConsoleHandleInfo *handleInfoPtr;
- for (handleInfoPtr = gConsoleHandleInfoList; handleInfoPtr; handleInfoPtr = handleInfoPtr->nextPtr) {
- if (handleInfoPtr->console == chanInfoPtr->handle) {
- return handleInfoPtr;
- }
- }
- return NULL;
-}
-
-/*
*----------------------------------------------------------------------
*
* TclWinOpenConsoleChannel --
@@ -2093,34 +1323,37 @@ FindConsoleInfo(const ConsoleChannelInfo *chanInfoPtr)
* Returns the new channel, or NULL.
*
* Side effects:
- * May open the channel.
+ * May open the channel
*
*----------------------------------------------------------------------
*/
+
Tcl_Channel
TclWinOpenConsoleChannel(
HANDLE handle,
char *channelName,
int permissions)
{
- ConsoleChannelInfo *chanInfoPtr;
- ConsoleHandleInfo *handleInfoPtr;
-
- /* A console handle can either be input or output, not both */
- if (permissions != TCL_READABLE && permissions != TCL_WRITABLE) {
- return NULL;
- }
+ char encoding[4 + TCL_INTEGER_SPACE];
+ ConsoleInfo *infoPtr;
+ DWORD id, modes;
ConsoleInit();
- chanInfoPtr = (ConsoleChannelInfo *)ckalloc(sizeof(*chanInfoPtr));
- memset(chanInfoPtr, 0, sizeof(*chanInfoPtr));
+ /*
+ * 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;
- chanInfoPtr->permissions = permissions;
- chanInfoPtr->handle = handle;
- chanInfoPtr->channel = (Tcl_Channel) NULL;
+ wsprintfA(encoding, "cp%d", GetConsoleCP());
- chanInfoPtr->threadId = Tcl_GetCurrentThread();
+ infoPtr->threadId = Tcl_GetCurrentThread();
/*
* Use the pointer for the name of the result channel. This keeps the
@@ -2128,7 +1361,10 @@ TclWinOpenConsoleChannel(
* for instance).
*/
- TclWinGenerateChannelName(channelName, "file", chanInfoPtr);
+ sprintf(channelName, "file%" TCL_I_MODIFIER "x", (size_t)infoPtr);
+
+ infoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName,
+ (ClientData) infoPtr, permissions);
if (permissions & TCL_READABLE) {
/*
@@ -2137,76 +1373,41 @@ TclWinOpenConsoleChannel(
* we only want to catch when complete lines are ready for reading.
*/
- chanInfoPtr->flags |= CONSOLE_READ_OPS;
- GetConsoleMode(handle, &chanInfoPtr->initMode);
-
-#ifdef OBSOLETE
- /* Why was priority being set on console input? Code smell */
- SetThreadPriority(infoPtr->reader.thread, THREAD_PRIORITY_HIGHEST);
-#endif
- } else {
- /* Already checked permissions is WRITABLE if not READABLE */
- /* TODO - enable ansi escape processing? */
+ 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->stopReader = CreateEvent(NULL, FALSE, FALSE, NULL);
+ infoPtr->readThread = CreateThread(NULL, 256, ConsoleReaderThread,
+ infoPtr, 0, &id);
+ SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST);
}
- /*
- * Global lock but that's ok. See comments top of file. Allocations
- * will happen only a few times in the life of a process and that too
- * generally at start up where only one thread is active.
- */
- AcquireSRWLockExclusive(&gConsoleLock); /*Allocate needs exclusive lock */
-
- handleInfoPtr = FindConsoleInfo(chanInfoPtr);
- if (handleInfoPtr == NULL) {
- /* Not found. Allocate one */
- handleInfoPtr = AllocateConsoleHandleInfo(handle, permissions);
- } else {
- /* Found. Its direction (read/write) better be the same */
- if (handleInfoPtr->permissions != permissions) {
- handleInfoPtr = NULL;
- }
+ if (permissions & TCL_WRITABLE) {
+ infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
+ infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
+ infoPtr->stopWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
+ infoPtr->writeThread = CreateThread(NULL, 256, ConsoleWriterThread,
+ infoPtr, 0, &id);
+ SetThreadPriority(infoPtr->writeThread, THREAD_PRIORITY_HIGHEST);
}
- if (handleInfoPtr == NULL) {
- ReleaseSRWLockExclusive(&gConsoleLock);
- if (permissions == TCL_READABLE) {
- SetConsoleMode(handle, chanInfoPtr->initMode);
- }
- ckfree(chanInfoPtr);
- return NULL;
- }
-
- /*
- * There is effectively a reference to this structure from the Tcl
- * channel subsystem. So record that. This reference will be dropped
- * when the Tcl channel is closed.
- */
- chanInfoPtr->numRefs = 1;
-
- /*
- * Need to keep track of number of referencing channels for closing.
- * The pointer is safe since there is a reference held to it from
- * gConsoleHandleInfoList but still need to lock the structure itself
- */
- AcquireSRWLockExclusive(&handleInfoPtr->lock);
- handleInfoPtr->numRefs += 1;
- ReleaseSRWLockExclusive(&handleInfoPtr->lock);
-
- ReleaseSRWLockExclusive(&gConsoleLock);
-
- /* Note Tcl_CreateChannel never fails other than panic on error */
- chanInfoPtr->channel = Tcl_CreateChannel(&consoleChannelType, channelName,
- chanInfoPtr, permissions);
-
/*
- * Consoles have default translation of auto and ^Z eof char, which means
+ * 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, chanInfoPtr->channel, "-translation", "auto");
- Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-eofchar", "\x1A {}");
- Tcl_SetChannelOption(NULL, chanInfoPtr->channel, "-encoding", "utf-16");
- return chanInfoPtr->channel;
+ Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
+ Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
+ if (tclWinProcs->useWide)
+ Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", "unicode");
+ else
+ Tcl_SetChannelOption(NULL, infoPtr->channel, "-encoding", encoding);
+
+ return infoPtr->channel;
}
/*
@@ -2227,221 +1428,35 @@ TclWinOpenConsoleChannel(
static void
ConsoleThreadActionProc(
- void *instanceData,
+ ClientData instanceData,
int action)
{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
-
- /* No need for any locks as no other thread will be writing to it */
- if (action == TCL_CHANNEL_THREAD_INSERT) {
- ConsoleInit(); /* Needed to set up event source handlers for this thread */
- chanInfoPtr->threadId = Tcl_GetCurrentThread();
- } else {
- chanInfoPtr->threadId = NULL;
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ConsoleSetOptionProc --
- *
- * Sets an option on a channel.
- *
- * Results:
- * A standard Tcl result. Also sets the interp's result on error if
- * interp is not NULL.
- *
- * Side effects:
- * May modify an option on a console. Sets Error message if needed (by
- * calling Tcl_BadChannelOption).
- *
- *----------------------------------------------------------------------
- */
-static int
-ConsoleSetOptionProc(
- void *instanceData, /* File state. */
- Tcl_Interp *interp, /* For error reporting - can be NULL. */
- const char *optionName, /* Which option to set? */
- const char *value) /* New value for option. */
-{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- int len = strlen(optionName);
- int vlen = strlen(value);
-
- /*
- * Option -inputmode normal|password|raw
- */
-
- if ((chanInfoPtr->flags & CONSOLE_READ_OPS) && (len > 1) &&
- (strncmp(optionName, "-inputmode", len) == 0)) {
- DWORD mode;
-
- if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) {
- Tcl_WinConvertError(GetLastError());
- if (interp != NULL) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't read console mode: %s",
- Tcl_PosixError(interp)));
- }
- return TCL_ERROR;
- }
- if (strncasecmp(value, "NORMAL", vlen) == 0) {
- mode |=
- ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT;
- } else if (strncasecmp(value, "PASSWORD", vlen) == 0) {
- mode |= ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT;
- mode &= ~ENABLE_ECHO_INPUT;
- } else if (strncasecmp(value, "RAW", vlen) == 0) {
- mode &= ~(ENABLE_ECHO_INPUT | ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT);
- } else if (strncasecmp(value, "RESET", vlen) == 0) {
- /*
- * Reset to the initial mode, whatever that is.
- */
- mode = chanInfoPtr->initMode;
- } else {
- if (interp) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "bad mode \"%s\" for -inputmode: must be"
- " normal, password, raw, or reset", value));
- Tcl_SetErrorCode(interp, "TCL", "OPERATION", "FCONFIGURE",
- "VALUE", (char *)NULL);
- }
- return TCL_ERROR;
- }
- if (SetConsoleMode(chanInfoPtr->handle, mode) == 0) {
- Tcl_WinConvertError(GetLastError());
- if (interp != NULL) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't set console mode: %s",
- Tcl_PosixError(interp)));
- }
- return TCL_ERROR;
- }
-
- return TCL_OK;
- }
-
- if (chanInfoPtr->flags & CONSOLE_READ_OPS) {
- return Tcl_BadChannelOption(interp, optionName, "inputmode");
- } else {
- return Tcl_BadChannelOption(interp, optionName, "");
- }
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * ConsoleGetOptionProc --
- *
- * Gets a mode associated with an IO channel. If the optionName arg is
- * non-NULL, retrieves the value of that option. If the optionName arg is
- * NULL, retrieves a list of alternating option names and values for the
- * given channel.
- *
- * Results:
- * A standard Tcl result. Also sets the supplied DString to the string
- * value of the option(s) returned. Sets error message if needed
- * (by calling Tcl_BadChannelOption).
- *
- *----------------------------------------------------------------------
- */
-
-static int
-ConsoleGetOptionProc(
- void *instanceData, /* File state. */
- Tcl_Interp *interp, /* For error reporting - can be NULL. */
- const char *optionName, /* Option to get. */
- Tcl_DString *dsPtr) /* Where to store value(s). */
-{
- ConsoleChannelInfo *chanInfoPtr = (ConsoleChannelInfo *)instanceData;
- int valid = 0; /* Flag if valid option parsed. */
- unsigned int len;
- char buf[TCL_INTEGER_SPACE];
-
- if (optionName == NULL) {
- len = 0;
- } else {
- len = strlen(optionName);
- }
+ ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
- /*
- * Get option -inputmode
- *
- * This is a great simplification of the underlying reality, but actually
- * represents what almost all scripts really want to know.
+ /* 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.
*/
- if (chanInfoPtr->flags & CONSOLE_READ_OPS) {
- if (len == 0) {
- Tcl_DStringAppendElement(dsPtr, "-inputmode");
- }
- if (len==0 || (len>1 && strncmp(optionName, "-inputmode", len)==0)) {
- DWORD mode;
-
- valid = 1;
- if (GetConsoleMode(chanInfoPtr->handle, &mode) == 0) {
- Tcl_WinConvertError(GetLastError());
- if (interp != NULL) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't read console mode: %s",
- Tcl_PosixError(interp)));
- }
- return TCL_ERROR;
- }
- if (mode & ENABLE_LINE_INPUT) {
- if (mode & ENABLE_ECHO_INPUT) {
- Tcl_DStringAppendElement(dsPtr, "normal");
- } else {
- Tcl_DStringAppendElement(dsPtr, "password");
- }
- } else {
- Tcl_DStringAppendElement(dsPtr, "raw");
- }
- }
- } else {
+ Tcl_MutexLock(&consoleMutex);
+ if (action == TCL_CHANNEL_THREAD_INSERT) {
/*
- * Output channel. Get option -winsize
- * Option is readonly and returned by [fconfigure chan -winsize] but not
- * returned by [fconfigure chan] without explicit option name.
+ * 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.
*/
- if (len == 0) {
- Tcl_DStringAppendElement(dsPtr, "-winsize");
- }
- if (len == 0 || (len > 1 && strncmp(optionName, "-winsize", len) == 0)) {
- CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
-
- valid = 1;
- if (!GetConsoleScreenBufferInfo(chanInfoPtr->handle,
- &consoleInfo)) {
- Tcl_WinConvertError(GetLastError());
- if (interp != NULL) {
- Tcl_SetObjResult(interp, Tcl_ObjPrintf(
- "couldn't read console size: %s",
- Tcl_PosixError(interp)));
- }
- return TCL_ERROR;
- }
- Tcl_DStringStartSublist(dsPtr);
- snprintf(buf, sizeof(buf), "%d",
- consoleInfo.srWindow.Right - consoleInfo.srWindow.Left + 1);
- Tcl_DStringAppendElement(dsPtr, buf);
- snprintf(buf, sizeof(buf), "%d",
- consoleInfo.srWindow.Bottom - consoleInfo.srWindow.Top + 1);
- Tcl_DStringAppendElement(dsPtr, buf);
- Tcl_DStringEndSublist(dsPtr);
+ ConsoleInit();
+ if (infoPtr->channel != NULL) {
+ infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel);
}
- }
-
- if (valid) {
- return TCL_OK;
- }
- if (chanInfoPtr->flags & CONSOLE_READ_OPS) {
- return Tcl_BadChannelOption(interp, optionName, "inputmode");
} else {
- return Tcl_BadChannelOption(interp, optionName, "winsize");
+ infoPtr->threadId = NULL;
}
+ Tcl_MutexUnlock(&consoleMutex);
}
/*