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);