Tcl Source Code

Check-in [a4bc297ce1]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Added test case for stdin on a windows console. Fixed fileevents and gets/read in non-blocking mode on console-based stdin.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | core-8-1-branch-old
Files: files | file ages | folders
SHA1: a4bc297ce10372490129e58286fda7aa680b78b8
User & Date: redman 1999-03-24 00:04:30
Context
1999-03-24
01:40
added test provided by bug report check-in: 7d19d397b9 user: surles tags: core-8-1-branch-old
00:04
Added test case for stdin on a windows console. Fixed fileevents and gets/read in non-blocking mode ... check-in: a4bc297ce1 user: redman tags: core-8-1-branch-old
1999-03-23
21:58
changed "test" namespace to "tcltest" check-in: f61f6d4d1d user: hershey tags: core-8-1-branch-old
Changes
Hide Diffs Unified Diffs Ignore Whitespace Patch

Changes to ChangeLog.












1
2
3
4
5
6
7










1999-03-22    <[email protected]>

	* tests/reg.test: 
	* generic/regc_color.c: Applied regexp bug fix from Henry Spencer.

1999-03-19    <[email protected]>

>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
1999-03-23    <[email protected]>

	* tests/winConsole.test:
	* win/tclWinConsole.c: Fixed problem with fileevents and gets from
	a console stdin.  Previously, fileevents were firing before an
	entire line was available for reading, which meant that when you
	did a gets or read, it blocked (even in nonblocking mode). Now, it
	should work the same as Unix: fileevents fire when an entire line
	is ready, and gets and read do not block in non-blocking mode.
	Added an interactive test case to check for this.

1999-03-22    <[email protected]>

	* tests/reg.test: 
	* generic/regc_color.c: Applied regexp bug fix from Henry Spencer.

1999-03-19    <[email protected]>

Added tests/winConsole.test.




































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# This file tests the tclWinConsole.c file.
#
# This file contains a collection of tests for one or more of the Tcl
# built-in commands.  Sourcing this file into Tcl runs the tests and
# generates output for errors.  No output means no errors were found.
#
# Copyright (c) 1999 by Scriptics Corporation.
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# RCS: @(#) $Id: winConsole.test,v 1.1.2.1 1999/03/24 00:04:30 redman Exp $

if {[lsearch [namespace children] ::tcltest] == -1} {
    source [file join [pwd] [file dirname [info script]] defs.tcl]
}


test winConsole-1.1 {Console file channel: non-blocking gets} \
	{pcOnly interactive} {
    
    set oldmode [fconfigure stdin]

    puts stdout "Enter abcdef<return> now: " nonewline
    flush stdout
    fileevent stdin readable {
	if {[gets stdin line] >= 0} {
	    set result $line
	} else {
	    set result "gets failed"
	}
    }

    fconfigure stdin -blocking 0 -buffering line

    set result {}
    vwait result

    #cleanup the fileevent
    fileevent stdin readable {}
    eval fconfigure stdin $oldmode

    set result

}  "abcdef"

#cleanup

::tcltest::cleanupTests
return

Changes to win/tclWinConsole.c.

5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
35
36
37
38
39
40
41
42
43

44
45
46
47
48
49
50
..
87
88
89
90
91
92
93


94
95
96
97
98
99
100
...
555
556
557
558
559
560
561

562
563
564
565
566
567
568
...
569
570
571
572
573
574
575




















576
577
578
579
580
581
582
...
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
...
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
...
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955







956

957
958
959
960

961
962
963

964
965
966

967
968
969
970
971
972
973
...
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
....
1173
1174
1175
1176
1177
1178
1179

1180
1181
1182
1183
1184
1185
1186
 *	and the "console" channel driver.
 *
 * Copyright (c) 1999 by Scriptics Corp.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinConsole.c,v 1.1.2.3 1999/03/14 18:55:11 stanton Exp $
 */

#include "tclWinInt.h"

#include <dos.h>
#include <fcntl.h>
#include <io.h>
................................................................................
#define CONSOLE_PENDING	(1<<0)	/* Message is pending in the queue. */
#define CONSOLE_ASYNC	(1<<1)	/* Channel is non-blocking. */

/*
 * Bit masks used in the sharedFlags field of the ConsoleInfo structure below.
 */

#define CONSOLE_EOF	(1<<2)	/* Console has reached EOF. */


/*
 * This structure describes per-instance data for a console based channel.
 */

typedef struct ConsoleInfo {
    HANDLE handle;
    int type;
................................................................................
				 * 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.  */


} ConsoleInfo;

typedef struct ThreadSpecificData {
    /*
     * The following pointer refers to the head of the list of consoles
     * that are being watched for file events.
     */
................................................................................
    int *errorCode)			/* Where to store error code. */
{
    ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
    DWORD count, bytesRead = 0;
    int result;

    *errorCode = 0;

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




















    
    /*
     * 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.
     */

................................................................................

static int
WaitForRead(
    ConsoleInfo *infoPtr,		/* Console state. */
    int blocking)		/* Indicates whether call should be
				 * blocking or not. */
{
    DWORD timeout, count, peekResult;
    HANDLE *handle = infoPtr->handle;
    INPUT_RECORD input;
    
    while (1) {
	/*
	 * Synchronize with the reader thread.
	 */
................................................................................
       
	timeout = blocking ? INFINITE : 0;
	if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) {
	    /*
	     * The reader thread is blocked waiting for data and the channel
	     * is in non-blocking mode.
	     */
	    
	    errno = EAGAIN;
	    return -1;
	}
	
	/*
	 * At this point, the two threads are synchronized, so it is safe
	 * to access shared state.
................................................................................
	 * If the console has hit EOF, it is always readable.
	 */
	
	if (infoPtr->readFlags & CONSOLE_EOF) {
	    return 1;
	}
	
	/*
	 * Check to see if there is any data sitting in the console.
	 * But first, remove any non-key events.
	 */
	
	while (peekResult = PeekConsoleInput(handle, &input, 1, &count)) {
	    if (count == 0) break;
	    if (input.EventType == KEY_EVENT) break;
	    
	    ReadConsoleInput(handle, &input, 1, &count);
	}
	
	if (!peekResult) {
	    /*
	     * Check to see if the peek failed because of EOF.
	     */
	    
	    TclWinConvertError(GetLastError());
	    
	    if (errno == EOF) {
		infoPtr->readFlags |= CONSOLE_EOF;
		return 1;
	    }








	    return -1;

	}

	/*
	 * We found some data in the console, so it must be readable.

	 */

	if (count > 0) {

	    return 1;
	}


	/*
	 * There wasn't any data available, so reset the thread and
	 * try again.
	 */
    
	ResetEvent(infoPtr->readable);
	SetEvent(infoPtr->startReader);
................................................................................
 */

static DWORD WINAPI
ConsoleReaderThread(LPVOID arg)
{
    ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
    HANDLE *handle = infoPtr->handle;
    DWORD count, peekResult;
    INPUT_RECORD input;

    for (;;) {
	/*
	 * Wait for the main thread to signal before attempting to wait.
	 */

	WaitForSingleObject(infoPtr->startReader, INFINITE);

	/*
	 * Try waiting for an event on the console.
	 */

	WaitForSingleObject(handle, INFINITE);

	count = 0;

	/* 
	 * Look for data on the console, but first ignore any events
	 * that are not KEY_EVENTs 
	 */

	while (peekResult = PeekConsoleInput(handle, &input, 1, &count)) {
	    if (input.EventType == KEY_EVENT) break;
	    if (count == 0) break;

	    ReadConsoleInput(handle, &input, 1, &count);
	}

	if (!peekResult) {
	    /*
	     * The error is a result of an EOF condition, so set the
	     * EOF bit before signalling the main thread.
	     */

	    TclWinConvertError(GetLastError());
	    
	    if (errno == EOF) {
		infoPtr->readFlags |= CONSOLE_EOF;
	    }
	}

	/*
	 * Signal the main thread by signalling the readable event and
	 * then waking up the notifier thread.
	 */
................................................................................
    infoPtr->threadId = Tcl_GetCurrentThread();

    if (permissions & TCL_READABLE) {
	infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->readThread = CreateThread(NULL, 8000, ConsoleReaderThread,
	        infoPtr, 0, &id);

    }

    if (permissions & TCL_WRITABLE) {
	infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->writeThread = CreateThread(NULL, 8000, ConsoleWriterThread,
	        infoPtr, 0, &id);






|







 







|
|
>







 







>
>







 







>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







<







 







<
<
<
<
<
<
<
<
<
|
<
<
<
|










>
>
>
>
>
>
>
|
>



|
>


<
>



>







 







|
<








<
<
<
<
<
<






|
|
|
|
|
|
|
|
|
|
|
|
|
|
<
<
<
<







 







>







5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
..
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
...
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
...
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
...
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
...
931
932
933
934
935
936
937

938
939
940
941
942
943
944
...
948
949
950
951
952
953
954









955



956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982

983
984
985
986
987
988
989
990
991
992
993
994
....
1015
1016
1017
1018
1019
1020
1021
1022

1023
1024
1025
1026
1027
1028
1029
1030






1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050




1051
1052
1053
1054
1055
1056
1057
....
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
 *	and the "console" channel driver.
 *
 * Copyright (c) 1999 by Scriptics Corp.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: tclWinConsole.c,v 1.1.2.4 1999/03/24 00:04:31 redman Exp $
 */

#include "tclWinInt.h"

#include <dos.h>
#include <fcntl.h>
#include <io.h>
................................................................................
#define CONSOLE_PENDING	(1<<0)	/* Message is pending in the queue. */
#define CONSOLE_ASYNC	(1<<1)	/* Channel is non-blocking. */

/*
 * Bit masks used in the sharedFlags field of the ConsoleInfo structure below.
 */

#define CONSOLE_EOF	  (1<<2)  /* Console has reached EOF. */
#define CONSOLE_EXTRABYTE (1<<3)  /* extra byte consumed while waiting for read
				     access */
/*
 * This structure describes per-instance data for a console based channel.
 */

typedef struct ConsoleInfo {
    HANDLE handle;
    int type;
................................................................................
				 * 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.  */
    char extraByte;             /* Buffer for extra character consumed by reade
				   thread. */
} ConsoleInfo;

typedef struct ThreadSpecificData {
    /*
     * The following pointer refers to the head of the list of consoles
     * that are being watched for file events.
     */
................................................................................
    int *errorCode)			/* Where to store error code. */
{
    ConsoleInfo *infoPtr = (ConsoleInfo *) instanceData;
    DWORD count, bytesRead = 0;
    int result;

    *errorCode = 0;

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

    if (infoPtr->readFlags & CONSOLE_EXTRABYTE) {
	/*
	 * The reader thread consumed 1 byte.
	 */

	*buf = infoPtr->extraByte;
	infoPtr->readFlags &= ~CONSOLE_EXTRABYTE;
	buf++;
	bufSize--;
	bytesRead = 1;

	/*
	 * If further read attempts would block, return what we have.
	 */

	if (result == 0) {
	    return bytesRead;
	}
    }
    
    /*
     * Attempt to read bufSize bytes.  The read will return immediately
     * if there is any data available.  Otherwise it will block until
     * at least one byte is available or an EOF occurs.
     */

................................................................................

static int
WaitForRead(
    ConsoleInfo *infoPtr,		/* Console state. */
    int blocking)		/* Indicates whether call should be
				 * blocking or not. */
{
    DWORD timeout, count;
    HANDLE *handle = infoPtr->handle;
    INPUT_RECORD input;
    
    while (1) {
	/*
	 * Synchronize with the reader thread.
	 */
................................................................................
       
	timeout = blocking ? INFINITE : 0;
	if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) {
	    /*
	     * The reader thread is blocked waiting for data and the channel
	     * is in non-blocking mode.
	     */

	    errno = EAGAIN;
	    return -1;
	}
	
	/*
	 * At this point, the two threads are synchronized, so it is safe
	 * to access shared state.
................................................................................
	 * 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_EXTRABYTE) {
		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_EXTRABYTE) {
	    return 1;
	}

	
	/*
	 * There wasn't any data available, so reset the thread and
	 * try again.
	 */
    
	ResetEvent(infoPtr->readable);
	SetEvent(infoPtr->startReader);
................................................................................
 */

static DWORD WINAPI
ConsoleReaderThread(LPVOID arg)
{
    ConsoleInfo *infoPtr = (ConsoleInfo *)arg;
    HANDLE *handle = infoPtr->handle;
    DWORD count;


    for (;;) {
	/*
	 * Wait for the main thread to signal before attempting to wait.
	 */

	WaitForSingleObject(infoPtr->startReader, INFINITE);







	count = 0;

	/* 
	 * Look for data on the console, but first ignore any events
	 * that are not KEY_EVENTs 
	 */
	if (ReadConsole(handle, &(infoPtr->extraByte), 1, &count, NULL)
		!= FALSE) {
	    /*
	     * One byte was consumed as a side effect of waiting for the
	     * console to become readable.
	     */
	    
	    infoPtr->readFlags |= CONSOLE_EXTRABYTE;
	} else {
	    DWORD err;
	    err = GetLastError();
	    
	    if (err == EOF) {
		infoPtr->readFlags = CONSOLE_EOF;




	    }
	}

	/*
	 * Signal the main thread by signalling the readable event and
	 * then waking up the notifier thread.
	 */
................................................................................
    infoPtr->threadId = Tcl_GetCurrentThread();

    if (permissions & TCL_READABLE) {
	infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->readThread = CreateThread(NULL, 8000, ConsoleReaderThread,
	        infoPtr, 0, &id);
	SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); 
    }

    if (permissions & TCL_WRITABLE) {
	infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL);
	infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL);
	infoPtr->writeThread = CreateThread(NULL, 8000, ConsoleWriterThread,
	        infoPtr, 0, &id);