public inbox for cygwin@cygwin.com
 help / color / mirror / Atom feed
* FIFO issues
@ 2022-09-18 21:45 Enrico Forestieri
  2022-09-19 19:15 ` Ken Brown
  0 siblings, 1 reply; 12+ messages in thread
From: Enrico Forestieri @ 2022-09-18 21:45 UTC (permalink / raw)
  To: cygwin

[-- Attachment #1: Type: text/plain, Size: 1469 bytes --]

Hi,

I think I am experiencing a problem with fifos on cygwin. The attached
C source (fifocomm.c) creates two pipes (/tmp/pipe.{in,out}), expecting
to receive inputs from /tmp/pipe.in and replying to /tmp/pipe.out.

Compiling this source on linux and launching it produces on the terminal

1) Opening pipes

and then the program waits for someone to write to /tmp/pipe.in.
Opening another terminal and launching the fifotest.sh script (also
attached) produces on the first terminal

1) Closing pipes
2) Opening pipes

and the program continues waiting for another input, while the other
terminal shows "You sent: foo"

Instead, on cygwin, after launching the program one gets:

1) Opening pipes
1) Closing pipes
2) Opening pipes
2) Closing pipes
3) Opening pipes
3) Closing pipes
4) Opening pipes
....
....

meaning that the pipes are continually closed and reopened even if
nobody is writing to /tmp/pipe.in.

Seemingly, the problem is the return value of read() on line 55.
As O_NONBLOCK is set, until no data is available for reading,
read() shouldn't block but should return -1 and set errno to EAGAIN.
After a client that opened the pipe for writing, closes it
(and no other client is using the pipe), read() should return 0 and
only at this point the pipes have to be closed and reopened.

However, on cygwin, read() returns 0 also when nobody is writing to the
input pipe causing the above ping pong. As already said, it works as it
should on linux.

-- 
Enrico

[-- Attachment #2: fifocomm.c --]
[-- Type: text/plain, Size: 3502 bytes --]

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FIFO_IN  "/tmp/pipe.in"
#define FIFO_OUT "/tmp/pipe.out"

#define FALSE 0
#define TRUE  1

typedef int bool;

void openpipes(void);
void closepipes(void);
int startpipe(char *name, bool inputpipe);
void endpipe(char *name, int fd);

int infd;
int outfd;
int count = 0;

int main(void)
{
    int nsel;
    bool done = FALSE;
    char const *msg = "You sent: ";
    int mlen = strlen(msg);
    fd_set readfds;
    FD_ZERO(&readfds);

    openpipes();

    do {
	FD_SET(infd, &readfds);
	do {
	    nsel = select(infd + 1, &readfds, 0, 0, 0);
	} while (nsel == -1 && (errno == EINTR || errno == EAGAIN));

	if (nsel == -1) {
	    perror("select");
	    exit(4);
	}

	if (FD_ISSET(infd, &readfds)) {
	    char buf[100] = "You sent: ";
	    int status;
	    int count = 0;
	    strcpy(buf, msg);
	    while ((status = read(infd, buf + mlen, sizeof(buf) - mlen - 1))) {
		if (status > 0) {
		    buf[mlen + status] = '\0';
		    if (write(outfd, buf, strlen(buf)) < 0) {
			if (buf[mlen + status - 1] == '\n')
			    buf[mlen + status - 1] = '\0';
			fprintf(stderr, "Error sending message: '%s': %s\n",
				buf, strerror(errno));
		    }
		    if (strncasecmp(buf + mlen, "quit", 4) == 0)
			done = TRUE;
		} else if (errno == EAGAIN) {
		    /* Nothing to read, continue */
		    break;
		} else {
		    /* An error occurred, bail out */
		    perror("read");
		    done = TRUE;
		    break;
		}
	    }
	    if (!done) {
		sleep(3);
		closepipes();
		openpipes();
		errno = 0;
	    }
	}
    } while (!done);

    closepipes();
    return 0;
}


void openpipes(void)
{
    fprintf(stderr, "%d) Opening pipes\n", ++count);

    infd = startpipe(FIFO_IN, TRUE);
    if (infd == -1)
	exit(1);

    outfd = startpipe(FIFO_OUT, FALSE);
    if (outfd == -1)
	exit(2);

    if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
	fprintf(stderr, "Could not set flags on pipe %s: %s\n",
		FIFO_OUT, strerror(errno));
	exit(3);
    }
}


void closepipes()
{
    fprintf(stderr, "%d) Closing pipes\n", count);

    endpipe(FIFO_IN, infd);
    endpipe(FIFO_OUT, outfd);
}


int startpipe(char *name, bool inputpipe)
{
    static bool stalepipe = FALSE;
    int fd;

    if (access(name, F_OK) == 0) {
	if (inputpipe) {
	    /* Let's see whether we have a stale pipe */
	    fd = open(name, O_WRONLY | O_NONBLOCK);
	    if (fd >= 0) {
		/* pipe exists and is used by another process */
		close(fd);
	    } else if (errno == ENXIO) {
		/* No process is reading from the other end */
		stalepipe = TRUE;
		endpipe(name, -1);
	    }
	} else if (stalepipe) {
	    endpipe(name, -1);
	    stalepipe = FALSE;
	}
	if (access(name, F_OK) == 0) {
	    fprintf(stderr, "Pipe %s already exists and is in use.\n", name);
	    return -1;
	}
    }

    if (mkfifo(name, 0600) < 0) {
	fprintf(stderr, "Could not create pipe %s: %s\n",
		name, strerror(errno));
	return -1;
    }

    fd = open(name, inputpipe ? (O_RDONLY | O_NONBLOCK) : O_RDWR);

    if (fd < 0) {
	fprintf(stderr, "Could not open pipe %s: %s\n",
		name, strerror(errno));
	endpipe(name, -1);
	return -1;
    }

    return fd;
}


void endpipe(char *name, int fd)
{
    if (fd >= 0 && close(fd) < 0)
	fprintf(stderr, "Could not close pipe %s: %s\n",
		name, strerror(errno));

    if (remove(name) < 0)
	fprintf(stderr, "Could not remove pipe %s: %s\n",
		name, strerror(errno));
}

[-- Attachment #3: fifotest.sh --]
[-- Type: application/x-sh, Size: 59 bytes --]

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-18 21:45 FIFO issues Enrico Forestieri
@ 2022-09-19 19:15 ` Ken Brown
  2022-09-19 19:50   ` Norton Allen
  0 siblings, 1 reply; 12+ messages in thread
From: Ken Brown @ 2022-09-19 19:15 UTC (permalink / raw)
  To: cygwin

On 9/18/2022 5:45 PM, Enrico Forestieri wrote:
> Hi,
> 
> I think I am experiencing a problem with fifos on cygwin. The attached
> C source (fifocomm.c) creates two pipes (/tmp/pipe.{in,out}), expecting
> to receive inputs from /tmp/pipe.in and replying to /tmp/pipe.out.
> 
> Compiling this source on linux and launching it produces on the terminal
> 
> 1) Opening pipes
> 
> and then the program waits for someone to write to /tmp/pipe.in.
> Opening another terminal and launching the fifotest.sh script (also
> attached) produces on the first terminal
> 
> 1) Closing pipes
> 2) Opening pipes
> 
> and the program continues waiting for another input, while the other
> terminal shows "You sent: foo"
> 
> Instead, on cygwin, after launching the program one gets:
> 
> 1) Opening pipes
> 1) Closing pipes
> 2) Opening pipes
> 2) Closing pipes
> 3) Opening pipes
> 3) Closing pipes
> 4) Opening pipes
> ....
> ....
> 
> meaning that the pipes are continually closed and reopened even if
> nobody is writing to /tmp/pipe.in.
> 
> Seemingly, the problem is the return value of read() on line 55.
> As O_NONBLOCK is set, until no data is available for reading,
> read() shouldn't block but should return -1 and set errno to EAGAIN.
> After a client that opened the pipe for writing, closes it
> (and no other client is using the pipe), read() should return 0 and
> only at this point the pipes have to be closed and reopened.
> 
> However, on cygwin, read() returns 0 also when nobody is writing to the
> input pipe causing the above ping pong. As already said, it works as it
> should on linux.

I see what's happening, but I don't see why it's a bug, and I don't understand 
why the Linux behavior is different.

On Cygwin, the call to 'select' in line 44 returns immediately with nsel == 1. 
This seems correct to me, since the man page for 'select' says, "A file 
descriptor is ready for reading if a read operation will not block; in 
particular, a file descriptor is also ready on end-of-file."  In the present 
case a read operation will not block for two reasons: first, O_NONBLOCK has been 
set; second, we're at EOF because no process has opened /tmp/pipe.in for 
writing.  Given that we're at EOF, the return value of 0 for the subsequent 
'read' is also correct.

On Linux, 'select' does not return immediately but instead waits for someone to 
write to the FIFO.

Can someone explain why Linux is right and Cygwin is wrong here?  I must be 
missing something obvious.

Ken

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-19 19:15 ` Ken Brown
@ 2022-09-19 19:50   ` Norton Allen
  2022-09-19 19:53     ` Norton Allen
  0 siblings, 1 reply; 12+ messages in thread
From: Norton Allen @ 2022-09-19 19:50 UTC (permalink / raw)
  To: Ken Brown, cygwin


On 9/19/2022 3:15 PM, Ken Brown wrote:
> On 9/18/2022 5:45 PM, Enrico Forestieri wrote:
>> Hi,
>>
>> I think I am experiencing a problem with fifos on cygwin. The attached
>> C source (fifocomm.c) creates two pipes (/tmp/pipe.{in,out}), expecting
>> to receive inputs from /tmp/pipe.in and replying to /tmp/pipe.out.
>>
>> Compiling this source on linux and launching it produces on the terminal
>>
>> 1) Opening pipes
>>
>> and then the program waits for someone to write to /tmp/pipe.in.
>> Opening another terminal and launching the fifotest.sh script (also
>> attached) produces on the first terminal
>>
>> 1) Closing pipes
>> 2) Opening pipes
>>
>> and the program continues waiting for another input, while the other
>> terminal shows "You sent: foo"
>>
>> Instead, on cygwin, after launching the program one gets:
>>
>> 1) Opening pipes
>> 1) Closing pipes
>> 2) Opening pipes
>> 2) Closing pipes
>> 3) Opening pipes
>> 3) Closing pipes
>> 4) Opening pipes
>> ....
>> ....
>>
>> meaning that the pipes are continually closed and reopened even if
>> nobody is writing to /tmp/pipe.in.
>>
>> Seemingly, the problem is the return value of read() on line 55.
>> As O_NONBLOCK is set, until no data is available for reading,
>> read() shouldn't block but should return -1 and set errno to EAGAIN.
>> After a client that opened the pipe for writing, closes it
>> (and no other client is using the pipe), read() should return 0 and
>> only at this point the pipes have to be closed and reopened.
>>
>> However, on cygwin, read() returns 0 also when nobody is writing to the
>> input pipe causing the above ping pong. As already said, it works as it
>> should on linux.
>
> I see what's happening, but I don't see why it's a bug, and I don't 
> understand why the Linux behavior is different.
>
> On Cygwin, the call to 'select' in line 44 returns immediately with 
> nsel == 1. This seems correct to me, since the man page for 'select' 
> says, "A file descriptor is ready for reading if a read operation will 
> not block; in particular, a file descriptor is also ready on 
> end-of-file."  In the present case a read operation will not block for 
> two reasons: first, O_NONBLOCK has been set; second, we're at EOF 
> because no process has opened /tmp/pipe.in for writing.  Given that 
> we're at EOF, the return value of 0 for the subsequent 'read' is also 
> correct.
>
> On Linux, 'select' does not return immediately but instead waits for 
> someone to write to the FIFO.
>
> Can someone explain why Linux is right and Cygwin is wrong here? I 
> must be missing something obvious.
>
> Ken

This is how I'm reading this, but I have not actually looked at or tried 
the posted code yet:

We use select() specifically when we are using non-blocking I/O. All the 
blocking happens in select() so we can track multiple sockets. If 
select() returns when there is no data available, it's not really doing 
its job, i.e. waiting for data. There are of course particular cases 
where something else (opening, closing) causes select() to return, which 
is normal and expected, but just because the reader is non-blocking is 
not a good reason for select() to return.



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-19 19:50   ` Norton Allen
@ 2022-09-19 19:53     ` Norton Allen
  2022-09-19 21:25       ` Ken Brown
  0 siblings, 1 reply; 12+ messages in thread
From: Norton Allen @ 2022-09-19 19:53 UTC (permalink / raw)
  To: cygwin

On 9/19/2022 3:50 PM, Norton Allen wrote:
>
> On 9/19/2022 3:15 PM, Ken Brown wrote:
>> On 9/18/2022 5:45 PM, Enrico Forestieri wrote:
>>> Hi,
>>>
>>> I think I am experiencing a problem with fifos on cygwin. The attached
>>> C source (fifocomm.c) creates two pipes (/tmp/pipe.{in,out}), expecting
>>> to receive inputs from /tmp/pipe.in and replying to /tmp/pipe.out.
>>>
>>> Compiling this source on linux and launching it produces on the 
>>> terminal
>>>
>>> 1) Opening pipes
>>>
>>> and then the program waits for someone to write to /tmp/pipe.in.
>>> Opening another terminal and launching the fifotest.sh script (also
>>> attached) produces on the first terminal
>>>
>>> 1) Closing pipes
>>> 2) Opening pipes
>>>
>>> and the program continues waiting for another input, while the other
>>> terminal shows "You sent: foo"
>>>
>>> Instead, on cygwin, after launching the program one gets:
>>>
>>> 1) Opening pipes
>>> 1) Closing pipes
>>> 2) Opening pipes
>>> 2) Closing pipes
>>> 3) Opening pipes
>>> 3) Closing pipes
>>> 4) Opening pipes
>>> ....
>>> ....
>>>
>>> meaning that the pipes are continually closed and reopened even if
>>> nobody is writing to /tmp/pipe.in.
>>>
>>> Seemingly, the problem is the return value of read() on line 55.
>>> As O_NONBLOCK is set, until no data is available for reading,
>>> read() shouldn't block but should return -1 and set errno to EAGAIN.
>>> After a client that opened the pipe for writing, closes it
>>> (and no other client is using the pipe), read() should return 0 and
>>> only at this point the pipes have to be closed and reopened.
>>>
>>> However, on cygwin, read() returns 0 also when nobody is writing to the
>>> input pipe causing the above ping pong. As already said, it works as it
>>> should on linux.
>>
>> I see what's happening, but I don't see why it's a bug, and I don't 
>> understand why the Linux behavior is different.
>>
>> On Cygwin, the call to 'select' in line 44 returns immediately with 
>> nsel == 1. This seems correct to me, since the man page for 'select' 
>> says, "A file descriptor is ready for reading if a read operation 
>> will not block; in particular, a file descriptor is also ready on 
>> end-of-file."  In the present case a read operation will not block 
>> for two reasons: first, O_NONBLOCK has been set; second, we're at EOF 
>> because no process has opened /tmp/pipe.in for writing.  Given that 
>> we're at EOF, the return value of 0 for the subsequent 'read' is also 
>> correct.
>>
>> On Linux, 'select' does not return immediately but instead waits for 
>> someone to write to the FIFO.
>>
>> Can someone explain why Linux is right and Cygwin is wrong here? I 
>> must be missing something obvious.
>>
>> Ken
>
> This is how I'm reading this, but I have not actually looked at or 
> tried the posted code yet:
>
> We use select() specifically when we are using non-blocking I/O. All 
> the blocking happens in select() so we can track multiple sockets. If 
> select() returns when there is no data available, it's not really 
> doing its job, i.e. waiting for data. There are of course particular 
> cases where something else (opening, closing) causes select() to 
> return, which is normal and expected, but just because the reader is 
> non-blocking is not a good reason for select() to return.
>
I should also mention that I use cygwin pipes extensively and have not 
encountered this behavior, so maybe I am doing things differently.



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-19 19:53     ` Norton Allen
@ 2022-09-19 21:25       ` Ken Brown
  2022-09-19 21:28         ` Norton Allen
       [not found]         ` <1b28b650-b588-e34f-919e-e75f5a01196f@lyx.org>
  0 siblings, 2 replies; 12+ messages in thread
From: Ken Brown @ 2022-09-19 21:25 UTC (permalink / raw)
  To: cygwin

On 9/19/2022 3:53 PM, Norton Allen wrote:
> On 9/19/2022 3:50 PM, Norton Allen wrote:
>>
>> On 9/19/2022 3:15 PM, Ken Brown wrote:
>>> On 9/18/2022 5:45 PM, Enrico Forestieri wrote:
>>>> Hi,
>>>>
>>>> I think I am experiencing a problem with fifos on cygwin. The attached
>>>> C source (fifocomm.c) creates two pipes (/tmp/pipe.{in,out}), expecting
>>>> to receive inputs from /tmp/pipe.in and replying to /tmp/pipe.out.
>>>>
>>>> Compiling this source on linux and launching it produces on the terminal
>>>>
>>>> 1) Opening pipes
>>>>
>>>> and then the program waits for someone to write to /tmp/pipe.in.
>>>> Opening another terminal and launching the fifotest.sh script (also
>>>> attached) produces on the first terminal
>>>>
>>>> 1) Closing pipes
>>>> 2) Opening pipes
>>>>
>>>> and the program continues waiting for another input, while the other
>>>> terminal shows "You sent: foo"
>>>>
>>>> Instead, on cygwin, after launching the program one gets:
>>>>
>>>> 1) Opening pipes
>>>> 1) Closing pipes
>>>> 2) Opening pipes
>>>> 2) Closing pipes
>>>> 3) Opening pipes
>>>> 3) Closing pipes
>>>> 4) Opening pipes
>>>> ....
>>>> ....
>>>>
>>>> meaning that the pipes are continually closed and reopened even if
>>>> nobody is writing to /tmp/pipe.in.
>>>>
>>>> Seemingly, the problem is the return value of read() on line 55.
>>>> As O_NONBLOCK is set, until no data is available for reading,
>>>> read() shouldn't block but should return -1 and set errno to EAGAIN.
>>>> After a client that opened the pipe for writing, closes it
>>>> (and no other client is using the pipe), read() should return 0 and
>>>> only at this point the pipes have to be closed and reopened.
>>>>
>>>> However, on cygwin, read() returns 0 also when nobody is writing to the
>>>> input pipe causing the above ping pong. As already said, it works as it
>>>> should on linux.
>>>
>>> I see what's happening, but I don't see why it's a bug, and I don't 
>>> understand why the Linux behavior is different.
>>>
>>> On Cygwin, the call to 'select' in line 44 returns immediately with nsel == 
>>> 1. This seems correct to me, since the man page for 'select' says, "A file 
>>> descriptor is ready for reading if a read operation will not block; in 
>>> particular, a file descriptor is also ready on end-of-file."  In the present 
>>> case a read operation will not block for two reasons: first, O_NONBLOCK has 
>>> been set; second, we're at EOF because no process has opened /tmp/pipe.in for 
>>> writing.  Given that we're at EOF, the return value of 0 for the subsequent 
>>> 'read' is also correct.
>>>
>>> On Linux, 'select' does not return immediately but instead waits for someone 
>>> to write to the FIFO.
>>>
>>> Can someone explain why Linux is right and Cygwin is wrong here? I must be 
>>> missing something obvious.
>>>
>>> Ken
>>
>> This is how I'm reading this, but I have not actually looked at or tried the 
>> posted code yet:
>>
>> We use select() specifically when we are using non-blocking I/O. All the 
>> blocking happens in select() so we can track multiple sockets. If select() 
>> returns when there is no data available, it's not really doing its job, i.e. 
>> waiting for data. There are of course particular cases where something else 
>> (opening, closing) causes select() to return, which is normal and expected, 
>> but just because the reader is non-blocking is not a good reason for select() 
>> to return.
>>
> I should also mention that I use cygwin pipes extensively and have not 
> encountered this behavior, so maybe I am doing things differently.

You're probably not calling select to check for read ready on non-blocking FIFOs 
that have no writers.

In the meantime, I just checked what POSIX says about the meaning of "read 
ready" in select, and it's more clear than the Linux man page (quoted above) 
about the non-blocking aspect:  "A descriptor shall be considered ready for 
reading when a call to an input function with O_NONBLOCK clear would not block, 
whether or not the function would transfer data successfully. (The function 
might return data, an end-of-file indication, or an error other than one 
indicating that it is blocked, and in each of these cases the descriptor shall 
be considered ready for reading.)"

So I think the Linux man page was just imprecise about this, and select should 
not indicate read ready just because O_NONBLOCK was set.  But what about the EOF 
aspect?  In the OP's test case, the FIFO was opened for reading and there were 
no writers, so again it seems that select should return read ready and a 
subsequent read should return 0.  But Linux disagrees.

I did an internet search on this issue and found the following, which describes 
the situation we're discussing:

https://stackoverflow.com/questions/14594508/fifo-pipe-is-always-readable-in-select

According to that post, select on Linux will wait for a writer the first time 
it's called to check read readiness for a FIFO opened for reading with 
O_NONBLOCK set.  But if the writer then closes the FIFO, subsequent calls to 
select will always find the FIFO read ready (and read will return 0).  This 
behavior is not documented, as far as I can tell, and in fact it contradicts the 
existing documentation (both POSIX and Linux).  So I don't think someone trying 
to write a portable program should rely on it.

Ken

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-19 21:25       ` Ken Brown
@ 2022-09-19 21:28         ` Norton Allen
       [not found]         ` <1b28b650-b588-e34f-919e-e75f5a01196f@lyx.org>
  1 sibling, 0 replies; 12+ messages in thread
From: Norton Allen @ 2022-09-19 21:28 UTC (permalink / raw)
  To: Ken Brown, cygwin

On 9/19/2022 5:25 PM, Ken Brown wrote:
> You're probably not calling select to check for read ready on 
> non-blocking FIFOs that have no writers.

Exactly what I was thinking (while on the bus).



^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
       [not found]         ` <1b28b650-b588-e34f-919e-e75f5a01196f@lyx.org>
@ 2022-09-19 23:54           ` Ken Brown
  2022-09-20  3:51             ` [EXTERNAL] " Lavrentiev, Anton (NIH/NLM/NCBI) [C]
  2022-09-20  6:54             ` Enrico Forestieri
  0 siblings, 2 replies; 12+ messages in thread
From: Ken Brown @ 2022-09-19 23:54 UTC (permalink / raw)
  To: cygwin

On 9/19/2022 6:05 PM, Enrico Forestieri wrote:
> Ken Brown wrote:
> 
>  > I did an internet search on this issue and found the following, which 
> describes the
> 
>  > situation we're discussing:
> 
>> https://stackoverflow.com/questions/14594508/fifo-pipe-is-always-readable-in-select 
> 
>  > According to that post, select on Linux will wait for a writer the first time 
> it's
> 
>  > called to check read readiness for a FIFO opened for reading with O_NONBLOCK 
> set.
> 
>  > But if the writer then closes the FIFO, subsequent calls to select will 
> always find
> 
>  > the FIFO read ready (and read will return 0). This behavior is not 
> documented, as far as
> 
>  > I can tell, and in fact it contradicts the existing documentation (both POSIX 
> and Linux).
> 
>  > So I don't think someone trying to write a portable program should rely on it.
> 
> 
> Please, note that this code was working on cygwin the way it works on linux 
> until some
> 
> time ago, maybe last year, I am not sure. I also found this stackoverflow 
> discussion:
> 
> https://stackoverflow.com/questions/28851639/select-with-non-blocking-reads
> 
> I tried the code also on Solaris and NetBSD and it works exactly as on linux, so 
> I think
> 
> it is portable.

Then I guess I'm wrong.  I'm really puzzled, because it seems that none of these 
platforms agree with POSIX, which says the following in its 'read' documentation:

     When attempting to read from an empty pipe or FIFO:

         If no process has the pipe open for writing, read() shall return 0 to
         indicate end-of-file.

It seems that there's an exception: If no process has ever had the FIFO open for 
writing since it was opened for reading, then the FIFO is not considered to be 
at end-of-file.

I'll look into fixing this.  But I'd be more confident about it if I could find 
some documentation of the existing behavior.

Ken

^ permalink raw reply	[flat|nested] 12+ messages in thread

* RE: [EXTERNAL] Re: FIFO issues
  2022-09-19 23:54           ` Ken Brown
@ 2022-09-20  3:51             ` Lavrentiev, Anton (NIH/NLM/NCBI) [C]
  2022-09-20  6:54             ` Enrico Forestieri
  1 sibling, 0 replies; 12+ messages in thread
From: Lavrentiev, Anton (NIH/NLM/NCBI) [C] @ 2022-09-20  3:51 UTC (permalink / raw)
  To: Ken Brown, cygwin

> It seems that there's an exception: If no process has ever had the FIFO open for
> writing since it was opened for reading, then the FIFO is not considered to be
> at end-of-file.

IMO, when a virgin FIFO is read with a blocking read (of just one byte), it will block -- it will not return 0.
Meaning that such a FIFO was not ready for read (when the other end was not (yet) attached / written), and
also meaning that there was no EOF (which is obvious because a transaction has not even started).
select() should indicate just the same (and it does so on the discussed platforms) regardless of O_NONBLOCK.

Anton Lavrentiev
Contractor NIH/NLM/NCBI


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-19 23:54           ` Ken Brown
  2022-09-20  3:51             ` [EXTERNAL] " Lavrentiev, Anton (NIH/NLM/NCBI) [C]
@ 2022-09-20  6:54             ` Enrico Forestieri
  2022-09-20 13:18               ` Ken Brown
  1 sibling, 1 reply; 12+ messages in thread
From: Enrico Forestieri @ 2022-09-20  6:54 UTC (permalink / raw)
  To: cygwin

On Mon, Sep 19, 2022 at 07:54:11PM -0400, Ken Brown wrote:
> 
> On 9/19/2022 6:05 PM, Enrico Forestieri wrote:
> > Ken Brown wrote:
> > > 
> > > I did an internet search on this issue and found the following, which 
> > > describes the
> > > 
> > > situation we're discussing:
> > > 
> > > https://stackoverflow.com/questions/14594508/fifo-pipe-is-always-readable-in-select 
> > > 
> > > According to that post, select on Linux will wait for a writer the first time 
> > > it's
> > > 
> > > called to check read readiness for a FIFO opened for reading with O_NONBLOCK 
> > > set.
> > 
> > > But if the writer then closes the FIFO, subsequent calls to select will 
> > > always find
> > > 
> > > the FIFO read ready (and read will return 0). This behavior is not 
> > > documented, as far as
> > > 
> > > I can tell, and in fact it contradicts the existing documentation (both POSIX 
> > > and Linux).
> > 
> > > So I don't think someone trying to write a portable program should rely on it.
> > 
> > 
> > Please, note that this code was working on cygwin the way it works on linux 
> > until some
> > 
> > time ago, maybe last year, I am not sure. I also found this stackoverflow 
> > discussion:
> > 
> > https://stackoverflow.com/questions/28851639/select-with-non-blocking-reads
> > 
> > I tried the code also on Solaris and NetBSD and it works exactly as on linux, so 
> > I think
> > 
> > it is portable.
> 
> Then I guess I'm wrong.  I'm really puzzled, because it seems that none of these 
> platforms agree with POSIX, which says the following in its 'read' documentation:
> 
>      When attempting to read from an empty pipe or FIFO:
> 
>          If no process has the pipe open for writing, read() shall return 0 to
>          indicate end-of-file.
> 
> It seems that there's an exception: If no process has ever had the FIFO open for 
> writing since it was opened for reading, then the FIFO is not considered to be 
> at end-of-file.
> 
> I'll look into fixing this.  But I'd be more confident about it if I could find 
> some documentation of the existing behavior.

I compared the behavior of read() and select() on 3 different platforms.
My conclusion is that, actually, read() behaves the same on all of them,
whereas cygwin differs in the way select() works.

As regards the example code posted earlier, on all other platforms
select() returns only when the pipe is written by a client or, if a
timeout is set, it returns 0. Instead, on cygwin it always returns 1.

However, opening the input pipe with "O_RDWR | O_NONBLOCK" instead of
"O_RDONLY | O_NONBLOCK" then cygwin behaves as all other platforms!

-- 
Enrico

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-20  6:54             ` Enrico Forestieri
@ 2022-09-20 13:18               ` Ken Brown
  2022-09-20 17:20                 ` Enrico Forestieri
  0 siblings, 1 reply; 12+ messages in thread
From: Ken Brown @ 2022-09-20 13:18 UTC (permalink / raw)
  To: cygwin

On 9/20/2022 2:54 AM, Enrico Forestieri wrote:
> On Mon, Sep 19, 2022 at 07:54:11PM -0400, Ken Brown wrote:
>> Then I guess I'm wrong.  I'm really puzzled, because it seems that none of these
>> platforms agree with POSIX, which says the following in its 'read' documentation:
>>
>>       When attempting to read from an empty pipe or FIFO:
>>
>>           If no process has the pipe open for writing, read() shall return 0 to
>>           indicate end-of-file.
>>
>> It seems that there's an exception: If no process has ever had the FIFO open for
>> writing since it was opened for reading, then the FIFO is not considered to be
>> at end-of-file.
>>
>> I'll look into fixing this.  But I'd be more confident about it if I could find
>> some documentation of the existing behavior.
> 
> I compared the behavior of read() and select() on 3 different platforms.
> My conclusion is that, actually, read() behaves the same on all of them,
> whereas cygwin differs in the way select() works.

Then I'm even more confused than I was before.  Are you saying that there are 
situations in which read() reports EOF but select() does not report read ready? 
Could you post the code you used for testing?

Thanks.

Ken

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-20 13:18               ` Ken Brown
@ 2022-09-20 17:20                 ` Enrico Forestieri
  2022-09-23 15:36                   ` Ken Brown
  0 siblings, 1 reply; 12+ messages in thread
From: Enrico Forestieri @ 2022-09-20 17:20 UTC (permalink / raw)
  To: cygwin

[-- Attachment #1: Type: text/plain, Size: 1145 bytes --]

On Tue, Sep 20, 2022 at 06:18:54PM -0700, Ken Brown wrote:
> 
> On 9/20/2022 2:54 AM, Enrico Forestieri wrote:
> > 
> >  I compared the behavior of read() and select() on 3 different platforms.
> >  My conclusion is that, actually, read() behaves the same on all of them,
> >  whereas cygwin differs in the way select() works.
> 
> Then I'm even more confused than I was before. Are you saying that
> there are situations in which read() reports EOF but select() does not
> report read ready? Could you post the code you used for testing?

I simply introduced a timeout for select() such that it returns on any
platform. On cygwin, when using "O_RDONLY | O_NONBLOCK" for the input
pipe, select() always returns 1, while on all other platforms it returns 0
unless a client actually opens it for writing.

As the "if (FD_ISSET(infd, &readfds))" block would not be entered on the
other platforms, I replaced it with "if (TRUE)" so that the read() is
always executed. In this conditions, the output is the same on all
platforms, meaning that the issue is the select() and not the read() call.

This is the attached fifocomm-test.c file.

-- 
Enrico

[-- Attachment #2: fifocomm-test.c --]
[-- Type: text/plain, Size: 3661 bytes --]

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

#define FIFO_IN  "/tmp/pipe.in"
#define FIFO_OUT "/tmp/pipe.out"

#define FALSE 0
#define TRUE  1

typedef int bool;

void openpipes(void);
void closepipes(void);
int startpipe(char *name, bool inputpipe);
void endpipe(char *name, int fd);

int infd;
int outfd;
int count = 0;

int main(void)
{
    int nsel;
    bool done = FALSE;
    char const *msg = "You sent: ";
    int mlen = strlen(msg);
    fd_set readfds;
    FD_ZERO(&readfds);

    openpipes();

    do {
	FD_SET(infd, &readfds);
	struct timeval wait_tm = { 0l, 200000l }; /* 200 millisecs */
	do {
	    nsel = select(infd + 1, &readfds, 0, 0, &wait_tm);
	} while (nsel == -1 && (errno == EINTR || errno == EAGAIN));

	if (nsel == -1) {
	    perror("select");
	    exit(4);
	}

	if (TRUE) {
	    char buf[100] = "You sent: ";
	    int status;
	    int count = 0;
	    strcpy(buf, msg);
	    while ((status = read(infd, buf + mlen, sizeof(buf) - mlen - 1))) {
		if (status > 0) {
		    buf[mlen + status] = '\0';
		    if (write(outfd, buf, strlen(buf)) < 0) {
			if (buf[mlen + status - 1] == '\n')
			    buf[mlen + status - 1] = '\0';
			fprintf(stderr, "Error sending message: '%s': %s\n",
				buf, strerror(errno));
		    }
		    if (strncasecmp(buf + mlen, "quit", 4) == 0)
			done = TRUE;
		} else if (errno == EAGAIN) {
		    /* Nothing to read, continue */
		    break;
		} else {
		    /* An error occurred, bail out */
		    perror("read");
		    done = TRUE;
		    break;
		}
	    }
	    if (!done) {
		sleep(3);
		closepipes();
		openpipes();
		errno = 0;
	    }
	}
    } while (!done);

    closepipes();
    return 0;
}


void openpipes(void)
{
    fprintf(stderr, "%d) Opening pipes\n", ++count);

    infd = startpipe(FIFO_IN, TRUE);
    if (infd == -1)
	exit(1);

    outfd = startpipe(FIFO_OUT, FALSE);
    if (outfd == -1)
	exit(2);

    if (fcntl(outfd, F_SETFL, O_NONBLOCK) < 0) {
	fprintf(stderr, "Could not set flags on pipe %s: %s\n",
		FIFO_OUT, strerror(errno));
	exit(3);
    }
}


void closepipes()
{
    fprintf(stderr, "%d) Closing pipes\n", count);

    endpipe(FIFO_IN, infd);
    endpipe(FIFO_OUT, outfd);
}


int startpipe(char *name, bool inputpipe)
{
    static bool stalepipe = FALSE;
    int fd;

    if (access(name, F_OK) == 0) {
	if (inputpipe) {
	    /* Let's see whether we have a stale pipe */
	    fd = open(name, O_WRONLY | O_NONBLOCK);
	    if (fd >= 0) {
		/* pipe exists and is used by another process */
		close(fd);
	    } else if (errno == ENXIO) {
		/* No process is reading from the other end */
		fprintf(stderr, "Removing stale pipe %s\n", name);
		stalepipe = TRUE;
		endpipe(name, -1);
	    }
	} else if (stalepipe) {
	    fprintf(stderr, "Removing stale pipe %s\n", name);
	    endpipe(name, -1);
	    stalepipe = FALSE;
	}
	if (access(name, F_OK) == 0) {
	    fprintf(stderr, "Pipe %s already exists and is in use.\n", name);
	    return -1;
	}
    }

    if (mkfifo(name, 0600) < 0) {
	fprintf(stderr, "Could not create pipe %s: %s\n",
		name, strerror(errno));
	return -1;
    }

    fd = open(name, inputpipe ? (O_RDONLY | O_NONBLOCK) : O_RDWR);

    if (fd < 0) {
	fprintf(stderr, "Could not open pipe %s: %s\n",
		name, strerror(errno));
	endpipe(name, -1);
	return -1;
    }

    return fd;
}


void endpipe(char *name, int fd)
{
    if (fd >= 0 && close(fd) < 0)
	fprintf(stderr, "Could not close pipe %s: %s\n",
		name, strerror(errno));

    if (remove(name) < 0)
	fprintf(stderr, "Could not remove pipe %s: %s\n",
		name, strerror(errno));
}

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: FIFO issues
  2022-09-20 17:20                 ` Enrico Forestieri
@ 2022-09-23 15:36                   ` Ken Brown
  0 siblings, 0 replies; 12+ messages in thread
From: Ken Brown @ 2022-09-23 15:36 UTC (permalink / raw)
  To: cygwin

[-- Attachment #1: Type: text/plain, Size: 1426 bytes --]

On 9/20/2022 1:20 PM, Enrico Forestieri wrote:
> On Tue, Sep 20, 2022 at 06:18:54PM -0700, Ken Brown wrote:
>>
>> On 9/20/2022 2:54 AM, Enrico Forestieri wrote:
>>>
>>>   I compared the behavior of read() and select() on 3 different platforms.
>>>   My conclusion is that, actually, read() behaves the same on all of them,
>>>   whereas cygwin differs in the way select() works.
>>
>> Then I'm even more confused than I was before. Are you saying that
>> there are situations in which read() reports EOF but select() does not
>> report read ready? Could you post the code you used for testing?
> 
> I simply introduced a timeout for select() such that it returns on any
> platform. On cygwin, when using "O_RDONLY | O_NONBLOCK" for the input
> pipe, select() always returns 1, while on all other platforms it returns 0
> unless a client actually opens it for writing.
> 
> As the "if (FD_ISSET(infd, &readfds))" block would not be entered on the
> other platforms, I replaced it with "if (TRUE)" so that the read() is
> always executed. In this conditions, the output is the same on all
> platforms, meaning that the issue is the select() and not the read() call.
> 
> This is the attached fifocomm-test.c file.

I've just sent a patch to the cygwin-patches list that makes Cygwin behave the 
same as Linux.  Thanks for reporting this problem.

Ken

P.S. I'm attaching a simplified test case that I used for testing the patch.

[-- Attachment #2: select_eof_fifo.c --]
[-- Type: text/plain, Size: 1298 bytes --]

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <errno.h>
#include <sys/stat.h>

#define FIFO_PATH "/tmp/myfifo"

int
main ()
{
  int fd, tmpfd, nsel;
  fd_set readfds;
  struct timeval wait_tm = { 0l, 200000l }; /* 200 millisecs */

  if (unlink (FIFO_PATH) < 0  && errno != ENOENT)
    {
      perror ("unlink");
      exit (1);
    }

  if (mkfifo (FIFO_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH
	      | S_IWOTH) < 0)
    {
      perror ("mkfifo");
      exit (1);
    }

  printf ("Opening a FIFO for reading with O_NONBLOCK\n");
  if ((fd = open (FIFO_PATH, O_RDONLY | O_NONBLOCK)) < 0)
    {
      perror ("open");
      exit (1);
    }

  printf ("Testing for read ready...\n");
  FD_ZERO (&readfds);
  FD_SET (fd, &readfds);
  nsel = select (fd + 1, &readfds, NULL, NULL, &wait_tm);
  printf ("  select returned %d\n", nsel);

  printf ("Opening and closing FIFO for writing...\n");
  if ((tmpfd = open (FIFO_PATH, O_WRONLY)) < 0)
    {
      perror ("open");
      exit (1);
    }
  if (close (tmpfd) < 0)
    {
      perror ("close");
      exit (1);
    }

  FD_ZERO (&readfds);
  FD_SET (fd, &readfds);
  nsel = select (fd + 1, &readfds, NULL, NULL, &wait_tm);
  printf ("  now select returned %d\n", nsel);
}

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2022-09-23 15:37 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-18 21:45 FIFO issues Enrico Forestieri
2022-09-19 19:15 ` Ken Brown
2022-09-19 19:50   ` Norton Allen
2022-09-19 19:53     ` Norton Allen
2022-09-19 21:25       ` Ken Brown
2022-09-19 21:28         ` Norton Allen
     [not found]         ` <1b28b650-b588-e34f-919e-e75f5a01196f@lyx.org>
2022-09-19 23:54           ` Ken Brown
2022-09-20  3:51             ` [EXTERNAL] " Lavrentiev, Anton (NIH/NLM/NCBI) [C]
2022-09-20  6:54             ` Enrico Forestieri
2022-09-20 13:18               ` Ken Brown
2022-09-20 17:20                 ` Enrico Forestieri
2022-09-23 15:36                   ` Ken Brown

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).