Skip to content

Commit 009d24f

Browse files
authored
Merge pull request #1409 from clasp-developers/fix-file-listen
When poll/select indicate non-blocking then check for bytes
2 parents 5e83946 + 5d0deac commit 009d24f

File tree

4 files changed

+106
-64
lines changed

4 files changed

+106
-64
lines changed

RELEASE_NOTES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
## Fixes
44
* Ensure that `print-unreadable-object` can accept output stream designators.
55
* Set stream column to zero after printing the prompt in a REPL. Fixes [#1348][].
6+
* Return correct values for `listen` when applied to file streams. This is done
7+
by checking for available bytes using read when poll/select indicate the next
8+
read will not block. Otherwise use non-blocking read. Fixes [#1404][].
69

710
# Version 2.1.0 (LLVM14) 2023-01-01
811

@@ -221,3 +224,4 @@ passing, and will miss the insightful conversations with him.
221224
[#1368]: https://github.com/clasp-developers/clasp/issues/1368
222225
[#1390]: https://github.com/clasp-developers/clasp/issues/1390
223226
[#1392]: https://github.com/clasp-developers/clasp/issues/1392
227+
[#1404]: https://github.com/clasp-developers/clasp/issues/1404

include/clasp/core/lispStream.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,7 @@ T_sp cl__close(T_sp strm, T_sp abort = nil<T_O>());
189189
#define CLASP_LISTEN_NO_CHAR 0
190190
#define CLASP_LISTEN_AVAILABLE 1
191191
#define CLASP_LISTEN_EOF -1
192+
#define CLASP_LISTEN_UNKNOWN -3
192193

193194
typedef claspCharacter (*cl_eformat_decoder)(T_sp stream, unsigned char **buffer, unsigned char *buffer_end);
194195
typedef int (*cl_eformat_encoder)(T_sp stream, unsigned char *buffer, claspCharacter c);

src/core/lispStream.cc

Lines changed: 89 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ void StreamCursor::backup(T_sp strm, claspCharacter c) {
380380
const FileOps &duplicate_dispatch_table(const FileOps &ops);
381381
const FileOps &stream_dispatch_table(T_sp strm);
382382

383-
static int flisten(T_sp, FILE *);
384-
static int file_listen(T_sp, int);
383+
static int file_listen(T_sp, FILE *);
384+
static int fd_listen(T_sp, int);
385385

386386
// static T_sp alloc_stream();
387387

@@ -2782,7 +2782,7 @@ static int io_file_listen(T_sp strm) {
27822782
}
27832783
}
27842784
}
2785-
return file_listen(strm, IOFileStreamDescriptor(strm));
2785+
return fd_listen(strm, IOFileStreamDescriptor(strm));
27862786
}
27872787

27882788
#if defined(CLASP_MS_WINDOWS_HOST)
@@ -2804,7 +2804,7 @@ static void io_file_clear_input(T_sp strm) {
28042804
/* Do not stop here: the FILE structure needs also to be flushed */
28052805
}
28062806
#endif
2807-
while (file_listen(strm, f) == CLASP_LISTEN_AVAILABLE) {
2807+
while (fd_listen(strm, f) == CLASP_LISTEN_AVAILABLE) {
28082808
claspCharacter c = eformat_read_char(strm);
28092809
if (c == EOF)
28102810
return;
@@ -3612,7 +3612,7 @@ static cl_index io_stream_read_byte8(T_sp strm, unsigned char *c, cl_index n) {
36123612
static int io_stream_listen(T_sp strm) {
36133613
if (StreamByteStack(strm).notnilp()) // != nil<T_O>())
36143614
return CLASP_LISTEN_AVAILABLE;
3615-
return flisten(strm, IOStreamStreamFile(strm));
3615+
return file_listen(strm, IOStreamStreamFile(strm));
36163616
}
36173617

36183618
static void io_stream_clear_input(T_sp strm) {
@@ -3625,7 +3625,7 @@ static void io_stream_clear_input(T_sp strm) {
36253625
/* Do not stop here: the FILE structure needs also to be flushed */
36263626
}
36273627
#endif
3628-
while (flisten(strm, fp) == CLASP_LISTEN_AVAILABLE) {
3628+
while (file_listen(strm, fp) == CLASP_LISTEN_AVAILABLE) {
36293629
clasp_disable_interrupts();
36303630
getc(fp);
36313631
clasp_enable_interrupts();
@@ -5054,60 +5054,8 @@ CL_DEFUN T_sp cl__close(T_sp strm, T_sp abort) { return core__closeSTAR(strm, ab
50545054
* BACKEND
50555055
*/
50565056

5057-
static int file_listen(T_sp stream, int fileno) {
5058-
#if !defined(CLASP_MS_WINDOWS_HOST)
5059-
#if defined(HAVE_POLL)
5060-
struct pollfd fds[1];
5061-
int retv;
5062-
fds[0].fd = fileno;
5063-
fds[0].events = POLLIN;
5064-
fds[0].revents = 0;
5065-
retv = poll(fds, 1, 0);
5066-
if (UNLIKELY(retv < 0))
5067-
file_libc_error(core::_sym_simpleStreamError, stream, "Error while listening to stream.", 0);
5068-
else if (retv > 0)
5069-
// POLLIN just means there's data, so that's fine.
5070-
if (fds[0].revents & POLLIN) {
5071-
return CLASP_LISTEN_AVAILABLE;
5072-
} else if (fds[0].revents & ~POLLHUP) { // error or something.
5073-
SIMPLE_ERROR(("Illegal revents value from poll -> %d%s%s%s%s%s%s"), fds[0].revents,
5074-
(fds[0].revents & POLLPRI) ? " POLLPRI" : "", (fds[0].revents & POLLOUT) ? " POLLOUT" : "",
5075-
(fds[0].revents & POLLOUT) ? " POLLRDHUP" : "", (fds[0].revents & POLLERR) ? " POLLERR" : "",
5076-
(fds[0].revents & POLLHUP) ? " POLLHUP" : "", (fds[0].revents & POLLNVAL) ? " POLLNVAL" : "");
5077-
} else {
5078-
// POLLHUP just means the other end hung up, which is kind of interesting
5079-
// but is not an error. Useful when the stream is a process output or something.
5080-
return CLASP_LISTEN_NO_CHAR;
5081-
}
5082-
else
5083-
return CLASP_LISTEN_NO_CHAR;
5084-
#elif defined(HAVE_SELECT)
5085-
fd_set fds;
5086-
int retv;
5087-
struct timeval tv = {0, 0};
5088-
/*
5089-
* Note that the following code is fragile. If the file is closed (/dev/null)
5090-
* then select() may return 1 (at least on OS X), so that we return a flag
5091-
* saying characters are available but will find none to read. See also the
5092-
* code in cl_clear_input().
5093-
*/
5094-
FD_ZERO(&fds);
5095-
FD_SET(fileno, &fds);
5096-
retv = select(fileno + 1, &fds, NULL, NULL, &tv);
5097-
if (UNLIKELY(retv < 0))
5098-
file_libc_error(core::_sym_simpleStreamError, stream, "Error while listening to stream.", 0);
5099-
else if (retv > 0)
5100-
return CLASP_LISTEN_AVAILABLE;
5101-
else
5102-
return CLASP_LISTEN_NO_CHAR;
5103-
#elif defined(FIONREAD)
5104-
{
5105-
long c = 0;
5106-
ioctl(fileno, FIONREAD, &c);
5107-
return (c > 0) ? CLASP_LISTEN_AVAILABLE : CLASP_LISTEN_NO_CHAR;
5108-
}
5109-
#endif /* FIONREAD */
5110-
#else
5057+
static int fd_listen(T_sp stream, int fileno) {
5058+
#ifdef CLASP_MS_WINDOWS_HOST
51115059
HANDLE hnd = (HANDLE)_get_osfhandle(fileno);
51125060
switch (GetFileType(hnd)) {
51135061
case FILE_TYPE_CHAR: {
@@ -5152,11 +5100,88 @@ static int file_listen(T_sp stream, int fileno) {
51525100
FEerror("Unsupported Windows file type: ~A", 1, make_fixnum(GetFileType(hnd)).raw_());
51535101
break;
51545102
}
5103+
#else
5104+
/* Method 1: poll, see POLL(2)
5105+
Method 2: select, see SELECT(2)
5106+
Method 3: ioctl FIONREAD, see FILIO(4)
5107+
Method 4: read a byte. Use non-blocking I/O if poll or select were not
5108+
available. */
5109+
int result;
5110+
#if defined(HAVE_POLL)
5111+
struct pollfd fd = {fileno, POLLIN, 0};
5112+
restart_poll:
5113+
result = poll(&fd, 1, 0);
5114+
if (UNLIKELY(result < 0)) {
5115+
if (errno == EINTR)
5116+
goto restart_poll;
5117+
goto listen_error;
5118+
}
5119+
if (fd.revents == 0) {
5120+
return CLASP_LISTEN_NO_CHAR;
5121+
}
5122+
/* When read() returns a result without blocking, this can also be
5123+
EOF! (Example: Linux and pipes.) We therefore refrain from simply
5124+
doing { return CLASP_LISTEN_AVAILABLE; } and instead try methods
5125+
3 and 4. */
5126+
#elif defined(HAVE_SELECT)
5127+
fd_set fds;
5128+
struct timeval tv = {0, 0};
5129+
FD_ZERO(&fds);
5130+
FD_SET(fileno, &fds);
5131+
restart_select:
5132+
result = select(fileno + 1, &fds, NULL, NULL, &tv);
5133+
if (UNLIKELY(result < 0)) {
5134+
if (errno == EINTR)
5135+
goto restart_select;
5136+
if (errno != EBADF) /* UNIX_LINUX returns EBADF for files! */
5137+
goto listen_error;
5138+
} else if (result == 0) {
5139+
return CLASP_LISTEN_NO_CHAR;
5140+
}
5141+
#endif
5142+
#ifdef FIONREAD
5143+
long c = 0;
5144+
if (ioctl(fileno, FIONREAD, &c) < 0) {
5145+
if (!((errno == ENOTTY) || IS_EINVAL))
5146+
goto listen_error;
5147+
return (c > 0) ? CLASP_LISTEN_AVAILABLE : CLASP_LISTEN_EOF;
5148+
}
5149+
#endif
5150+
#if !defined(HAVE_POLL) && !defined(HAVE_SELECT)
5151+
int flags = fcntl(fd, F_GETFL, 0);
5152+
#endif
5153+
int read_errno;
5154+
cl_index b;
5155+
restart_read:
5156+
#if !defined(HAVE_POLL) && !defined(HAVE_SELECT)
5157+
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
5158+
#endif
5159+
result = read(fileno, &b, 1);
5160+
read_errno = errno;
5161+
#if !defined(HAVE_POLL) && !defined(HAVE_SELECT)
5162+
fcntl(fd, F_SETFL, flags);
5163+
#endif
5164+
if (result < 0) {
5165+
if (read_errno == EINTR)
5166+
goto restart_read;
5167+
if (read_errno == EAGAIN || read_errno == EWOULDBLOCK)
5168+
return CLASP_LISTEN_NO_CHAR;
5169+
goto listen_error;
5170+
}
5171+
5172+
if (result == 0) {
5173+
return CLASP_LISTEN_EOF;
5174+
}
5175+
5176+
StreamByteStack(stream) = Cons_O::createList(make_fixnum(b));
5177+
return CLASP_LISTEN_AVAILABLE;
5178+
listen_error:
5179+
file_libc_error(core::_sym_simpleStreamError, stream, "Error while listening to stream.", 0);
51555180
#endif
5156-
return -3;
5181+
return CLASP_LISTEN_UNKNOWN;
51575182
}
51585183

5159-
static int flisten(T_sp stream, FILE *fp) {
5184+
static int file_listen(T_sp stream, FILE *fp) {
51605185
ASSERT(stream.notnilp());
51615186
int aux;
51625187
if (feof(fp))
@@ -5165,8 +5190,8 @@ static int flisten(T_sp stream, FILE *fp) {
51655190
if (FILE_CNT(fp) > 0)
51665191
return CLASP_LISTEN_AVAILABLE;
51675192
#endif
5168-
aux = file_listen(stream, fileno(fp));
5169-
if (aux != -3)
5193+
aux = fd_listen(stream, fileno(fp));
5194+
if (aux != CLASP_LISTEN_UNKNOWN)
51705195
return aux;
51715196
/* This code is portable, and implements the expected behavior for regular files.
51725197
It will fail on noninteractive streams. */

src/lisp/regression-tests/streams01.lisp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,18 @@
2020
(END-OF-FILE () (LISTEN S)))))
2121
(nil nil))
2222

23+
(test-type listen-4
24+
(progn
25+
(with-open-file (blah "nada.txt" :direction :output
26+
:if-does-not-exist :create
27+
:if-exists :supersede)
28+
(write-char #\a blah))
29+
(with-open-file (blah "nada.txt" :direction :input)
30+
(values (listen blah)
31+
(read-char blah)
32+
(listen blah))))
33+
(t #\a nil))
34+
2335
(test-expect-error broadcast-stream (make-broadcast-stream 1 2 3)
2436
:type type-error)
2537

0 commit comments

Comments
 (0)