Subject: kern/27185: kqueue: EOF on pipe gains no EVFILT_READ event
To: None <gnats-bugs@gnats.NetBSD.org>
From: Christian Biere <christianbiere@gmx.de>
List: netbsd-bugs
Date: 10/07/2004 22:44:21
>Number:         27185
>Category:       kern
>Synopsis:       kqueue: EOF on pipe gains no EVFILT_READ event
>Confidential:   no
>Severity:       non-critical
>Priority:       medium
>Responsible:    kern-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Thu Oct 07 20:45:01 UTC 2004
>Closed-Date:
>Last-Modified:
>Originator:     Christian Biere
>Release:        NetBSD 2.0G
>Organization:
>Environment:
System: NetBSD cyclonus 2.0G NetBSD 2.0G (STARSCREAM) #0: Tue Jul 20 02:41:48 CEST 2004 bin@cyclonus:/usr/obj/sys/arch/i386/compile/STARSCREAM i386
Architecture: i386
Machine: i386
>Description:
When using pipes for IPC, closing the write end of pipe doesn't cause
a EVFILT_READ kqueue event on the reader's side. It works using the
traditional poll() interface i.e., the reader gets a POLLHUP on the
corresponding file descriptor. If checked this on NetBSD 2.0G with
sources as of September 24th, too.

>How-To-Repeat:

The following program demonstrates the problem. When compiled with
-DUSE_KQUEUE, the child process doesn't terminate as it receives no
event when the parent process exits which implicitely closes the
pipe. If compiled to use poll() the child receives a POLLHUP event
and terminates.

Note the #if 0 ... #endif part, though. If the write end is closed before
the fork(), the child will also receive an event using kqueue(). So
this looks really like a bug and not like a semantic difference.

/*
 * To see the effect using when kqueue():
 * cc -DUSE_KQUEUE pipe_test.c -o pipe_test
 *
 * The same using poll():
 * cc pipe_test.c -o pipe_test
 */
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

static void fatal(const char *s);

#ifdef USE_KQUEUE
#include <sys/event.h>
#include <sys/time.h>

static void
wait_for_eof(int fd)
{
  int kq;
  struct kevent kev;
  static const struct timespec ts = { 0, 0 };

  kq = kqueue();
  if (-1 == kq)
    fatal("kqueue");

  EV_SET(&kev, fd, EVFILT_READ, EV_ADD, 0, 0, 0);
  if (kevent(kq, &kev, 1, 0, 0, &ts))
    fatal("kevent()");

  while (-1 == kevent(kq, NULL, 0, &kev, 1, NULL)) {
    if (errno != EINTR)
      fatal("kevent()");
  }
}

#else /* !USE_KQUEUE */

#include <poll.h>

static void
wait_for_eof(int fd)
{
  struct pollfd pfd;

  pfd.fd = fd;
  pfd.events = POLLIN;
  while (-1 == poll(&pfd, 1, INFTIM)) {
    if (errno != EINTR)
      fatal("poll()");
  }
}

#endif

static void
fatal(const char *s)
{
  perror(s);
  exit(EXIT_FAILURE);
}

int main(void)
{
  int fds[2];

  if (pipe(fds))
    fatal("pipe()");
  
  switch (fork()) {
  case -1:
    fatal("fork()");
    break;
  case 0: /* Child */
    close(fds[1]);
#if 0
    /* If the parent process exits before wait_for_eof() is reached,
     * the child does actually receive a EVFILT_READ event. */
    sleep(8);
#endif
    wait_for_eof(fds[0]);
    printf("Child terminates now\n");
    exit(EXIT_SUCCESS);
    break;
  default: /* Parent */
    close(fds[0]);
  }

  sleep(4);
  printf("Parent terminates now\n");
  exit(EXIT_SUCCESS);
}

>Fix:
>Release-Note:
>Audit-Trail:
>Unformatted: