On Thu, Mar 29, 2018 at 15:09 +0200, Lukas Larsson wrote:
> Hello,
> 
> I've been re-writing the polling mechanisms in the Erlang VM and stumbled
> across
> something that might be a bug in the OpenBSD implementation of kqueue.
> 
> When using EV_DISPATCH, the event is never triggered again after the EV_EOF
> flag has been delivered, even though there is more data to be read from the
> socket.
> 
> I've attached a smallish program that shows the problem.
> 
> The shortened ktrace output looks like this on OpenBSD 6.2:
> 
>  29672 a.out    0.012883 CALL  kevent(4,0x7f7ffffe8220,1,0,0,0)
>  29672 a.out    0.012888 STRU  struct kevent { ident=5, filter=EVFILT_READ,
> flags=0x81<EV_ADD|EV_DISPATCH>, fflags=0<>, data=0, udata=0x0 }
>  29672 a.out    0.012895 RET   kevent 0
>  29672 a.out    0.012904 CALL  kevent(4,0,0,0x7f7ffffe7cf0,32,0)
>  29672 a.out    0.013408 STRU  struct kevent { ident=5, filter=EVFILT_READ,
> flags=0x81<EV_ADD|EV_DISPATCH>, fflags=0<>, data=6, udata=0x0 }
>  29672 a.out    0.013493 RET   kevent 1
>  29672 a.out    0.013548 CALL  read(5,0x7f7ffffe8286,0x2)
>  29672 a.out    0.013562 RET   read 2
>  29672 a.out    0.013590 CALL  kevent(4,0x7f7ffffe8220,1,0,0,0)
>  29672 a.out    0.013594 STRU  struct kevent { ident=5, filter=EVFILT_READ,
> flags=0x84<EV_ENABLE|EV_DISPATCH>, fflags=0<>, data=0, udata=0x0 }
>  29672 a.out    0.013608 RET   kevent 0
>  29672 a.out    1.022228 CALL  kevent(4,0,0,0x7f7ffffe7cf0,32,0)
>  29672 a.out    1.022537 STRU  struct kevent { ident=5, filter=EVFILT_READ,
> flags=0x8081<EV_ADD|EV_DISPATCH|EV_EOF>, fflags=0<>, data=4, udata=0x0 }
>  29672 a.out    1.022572 RET   kevent 1
>  29672 a.out    1.022663 CALL  read(5,0x7f7ffffe8286,0x2)
>  29672 a.out    1.022707 RET   read 2
>  29672 a.out    1.022816 CALL  kevent(4,0x7f7ffffe8220,1,0,0,0)
>  29672 a.out    1.022822 STRU  struct kevent { ident=5, filter=EVFILT_READ,
> flags=0x84<EV_ENABLE|EV_DISPATCH>, fflags=0<>, data=0, udata=0x0 }
>  29672 a.out    1.022835 RET   kevent 0
>  29672 a.out    2.032238 CALL  kevent(4,0,0,0x7f7ffffe7cf0,32,0)
>  29672 a.out    5.277194 PSIG  SIGINT SIG_DFL
> 
> In this example I would have expected the last kevent call to return with
> EV_EOF and
> data set to 2, but it does not trigger again. If I don't use EV_DISPATCH,
> the event is
> triggered again and the program terminates.
> 
> Does anyone know if this is the expected behavior or a bug?
> 
> I've worked around this issue by using EV_ONESHOT instead of EV_DISPATCH on
> OpenBSD for now, but would like to use EV_DISPATCH in the future as I've
> found
> that it aligns better with the abstractions that I use, and could possibly
> be a little bit
> more performant.
> 
> Lukas
> 
> PS. If relevant, it seems like FreeBSD does behave the way that I expected,
> i.e.
> it triggers again for EV_DISPATCH after EV_EOF has been shown. DS.

> #include <stdio.h>
> #include <stdlib.h>
> #include <unistd.h>
> #include <stdint.h>
> #include <err.h>
> #include <sys/event.h>
> 
> #include <sys/socket.h>
> #include <sys/un.h>
> #include <netdb.h>
> #include <stdio.h>
> #include <stdarg.h>
> #include <string.h>
> 
> #define USE_DISPATCH 1
> 
> int main() {
>     struct addrinfo *addr;
>     struct addrinfo hints;
>     int kq, listen_s, fd = -1;
>     struct kevent evSet;
>     struct kevent evList[32];
> 
>     /* open a TCP socket */
>     memset(&hints, 0, sizeof hints);
>     hints.ai_family = PF_UNSPEC; /* any supported protocol */
>     hints.ai_flags = AI_PASSIVE; /* result for bind() */
>     hints.ai_socktype = SOCK_STREAM; /* TCP */
>     int error = getaddrinfo ("127.0.0.1", "8080", &hints, &addr);
>     if (error)
>         errx(1, "getaddrinfo failed: %s", gai_strerror(error));
>     listen_s = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
>     if (setsockopt(listen_s, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, 
> sizeof(int)) < 0)
>         errx(1, "setsockopt(SO_REUSEADDR) failed");
>     bind(listen_s, addr->ai_addr, addr->ai_addrlen);
>     listen(listen_s, 5);
> 
>     kq = kqueue();
> 
>     system("echo -n abcdef | nc -v -w 1 127.0.0.1 8080 &");
> 
>     EV_SET(&evSet, listen_s, EVFILT_READ, EV_ADD, 0, 0, NULL);
>     if (kevent(kq, &evSet, 1, NULL, 0, NULL) == -1)
>         err(1, "kevent");
> 
>     while(1) {
>         int i;
>         int nev = kevent(kq, NULL, 0, evList, 32, NULL);
>         for (i = 0; i < nev; i++) {
>             if (evList[i].ident == listen_s) {
>                 struct sockaddr_storage addr;
>                 socklen_t socklen = sizeof(addr);
>                 if (fd != -1)
>                     close(fd);
>                 fd = accept(evList[i].ident, (struct sockaddr *)&addr, 
> &socklen);
>                 printf("accepted %d\n", fd);
> #if USE_DISPATCH
>                 EV_SET(&evSet, fd, EVFILT_READ, EV_ADD|EV_DISPATCH, 0, 0, 
> NULL);
> #else
>                 EV_SET(&evSet, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
> #endif
>                 if (kevent(kq, &evSet, 1, NULL, 0, NULL) == -1)
>                     err(1, "kevent");
>             } else {
>                 if (evList[i].flags & EV_EOF && evList[i].data == 0) {
>                     printf("closing %d\n", fd);
>                     close(fd);
>                     fd = -1;
>                     exit(0);
>                 } else if (evList[i].filter == EVFILT_READ) {
>                     char buff[2];
>                     read(fd, buff, 2);
>                     if (evList[i].flags & EV_EOF) printf("EOF: ");
>                     printf("read %c%c from %d\n", buff[0], buff[1], fd);
> #if USE_DISPATCH
>                     EV_SET(&evSet, fd, EVFILT_READ, EV_ENABLE|EV_DISPATCH, 0, 
> 0, NULL);
>                     if (kevent(kq, &evSet, 1, NULL, 0, NULL) == -1)
>                         err(1, "kevent");
> #endif
>                     sleep(1);
>                 }
>             }
>         }
>     }
> }


Hi,

This appears to be an issue with reactivating disabled event sources
in kqueue_register.  Something along the lines of FreeBSD commits:

https://svnweb.freebsd.org/base?view=revision&revision=274560 and
https://reviews.freebsd.org/rS295786 where parent differential review
https://reviews.freebsd.org/D5307 has some additional comments.

In any case, by either porting their code (#else branch) or slightly
adjusting our own (I think that should be enough), I can no longer
reproduce the issue you've reported.  Please test and report back if
that solves your original issue.  Either variants will require
rigorous testing and a thorough review.

Cheers,
Mike

diff --git sys/kern/kern_event.c sys/kern/kern_event.c
index fb9cad360b1..8a999b24923 100644
--- sys/kern/kern_event.c
+++ sys/kern/kern_event.c
@@ -661,25 +661,39 @@ kqueue_register(struct kqueue *kq, struct kevent *kev, 
struct proc *p)
                kn->kn_fop->f_detach(kn);
                knote_drop(kn, p, p->p_fd);
                goto done;
        }
 
+#if 1
        if ((kev->flags & EV_DISABLE) &&
            ((kn->kn_status & KN_DISABLED) == 0)) {
                s = splhigh();
                kn->kn_status |= KN_DISABLED;
                splx(s);
        }
 
        if ((kev->flags & EV_ENABLE) && (kn->kn_status & KN_DISABLED)) {
                s = splhigh();
                kn->kn_status &= ~KN_DISABLED;
-               if ((kn->kn_status & KN_ACTIVE) &&
-                   ((kn->kn_status & KN_QUEUED) == 0))
-                       knote_enqueue(kn);
+               if (kn->kn_fop->f_event(kn, 0))
+                       KNOTE_ACTIVATE(kn);
                splx(s);
        }
+#else
+       s = splhigh();
+       if ((kev->flags & EV_ENABLE) != 0)
+               kn->kn_status &= ~KN_DISABLED;
+       else if ((kev->flags & EV_DISABLE) != 0)
+               kn->kn_status |= KN_DISABLED;
+
+       if (!(kn->kn_status & KN_DISABLED) && kn->kn_fop->f_event(kn, 0))
+               kn->kn_status |= KN_ACTIVE;
+       if ((kn->kn_status & (KN_ACTIVE | KN_DISABLED | KN_QUEUED)) ==
+           KN_ACTIVE)
+               knote_enqueue(kn);
+       splx(s);
+#endif
 
 done:
        if (fp != NULL)
                FRELE(fp, p);
        return (error);

Reply via email to