In kqueue
it's possible to submit a kevent
with EV_ONESHOT
or EV_DISPATCH
flags. The first deletes the kevent
after first event delivery, and the latter disables the kevent
after first event delivery.
The observable effect with both flags is that subsequent events will not be delivered unless the kevent
is re-added or re-enabled, respectively.
Are there other observable effects between EV_ONESHOT
and EV_DISPATCH
and how to choose one over the other?
The manual page for disabling a kevent
using EV_DISABLE
mentions that The filter itself is not disabled. Then, if I choose to use EV_DISPATCH
, the filter itself is not disabled. I don’t understand what effect that has. Could someone explain?
I'll answer my own question after having done more research.
I recommend reading the 2001 USENIX paper "Kqueue: A generic and scalable event notification facility” at http://people.freebsd.org/~jlemon/papers/kqueue.pdf.
When kernel receives a kevent
from user, it creates a knote
to represent the subscription and a filter to deal with the event source. The filter is invoked on event source activity such as packet arrival or signal delivery. The knote
is added to an active list for event delivery to user if the filter indicates so and the knote
is enabled. This is enough to understand the difference between EV_ONESHOT
and EV_DISPATCH
.
EV_ONESHOT
causes the knote
and the filter to be deleted after first event delivery. Thus the filter can no longer be called on event source activity. EV_DISPATCH
causes the knote
to be marked disabled after first event delivery, but the filter will remain to process event source activity. Whether this is a meaningful difference depends on the filter being used. Additionally, enable/disable is a faster operation than add/delete. The USENIX paper has some graphs on the performance.
I wrote a program (tested on macOS) to demonstrate the difference using EVFILT_SIGNAL
. Maybe someone will find this useful in the future.
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
int main()
{
struct timespec timeout = {.tv_sec = 0, .tv_nsec = 0};
sigblock(sigmask(SIGUSR1));
pid_t pid = getpid();
int kq = kqueue();
struct kevent e;
EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_DISPATCH, 0, 0, NULL);
// EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, 0, NULL);
kevent(kq, &e, 1, NULL, 0, NULL);
kill(pid, SIGUSR1);
kevent(kq, NULL, 0, &e, 1, &timeout);
printf("%jd\n", e.data);
// When EV_DISPATCH is used, the filter continues to record
// event source activity. When EV_ONESHOT is used, there is no
// longer a filter to record activity. In either way, kernel
// doesn't deliver events to user.
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);
kill(pid, SIGUSR1);
EV_SET(&e, SIGUSR1, EVFILT_SIGNAL, EV_ADD | EV_ENABLE, 0, 0, NULL);
kevent(kq, &e, 1, &e, 1, &timeout);
// Prints 3 or 0 with EV_DISPATCH or EV_ONESHOT, respectively.
printf("%jd\n", e.data);
close(kq);
}