Kqueue Error Handling

kqerror.c

This example aims to illustrate what happens when errors occur when the changelist is being processed. It uses EVFILT_SIGNAL to illustrate this, and a bad EVFILT_XXX number as the error inducing item.

A brief narrative of the code follows:

signal(SIGINT, SIG_IGN);                /*< Line 25 */
signal(SIGHUP, SIG_IGN);
signal(SIGTERM, SIG_IGN);
signal(SIGQUIT, SIG_IGN);               /*< Line 28 */

EV_SET(&ke_set[0], SIGINT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);

i = kevent(kq, &ke_set[0], 1, NULL, 0, NULL);
if (i == -1)
    err(1, "set kevent for SIGINT");    /*< Line 34 */

kill(getpid(), SIGINT);                 /*< Line 36 */

First we ignore the signals we’ll be using (lines 25 - 28), then we register a kevent for notification of SIGINT (lines 30 - 34), and send ourselves a SIGINT (line 36).

EV_SET(&ke_set[0], 42, -10, EV_ADD, 0, 0, NULL);    /*< Line 38 */
EV_SET(&ke_set[1], SIGHUP, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);

i = kevent(kq, ke_set, 2, ke_retr, 4, NULL);        /*< Line 41 */
if (i == -1)
    err(1, "kevent set sig!");

Try to register two more kevents. The first is invalid, the second is valid and lets us receive notification of SIGHUP. Note that in the call to kevent() we pass in ke_retr as well.

printf("first invalid kevent call returned %d\n", i);

for (j = 0; j < i; j++) { 
    if (ke_retr[j].flags & EV_ERROR) 
        printf("error! errno %d: %s\n", ke_retr[j].data,
            strerror(ke_retr[j].data));
    else
        printf("received SIG%s %d times\n", 
            sys_signame[ke_retr[j].ident], ke_retr[j].data);
}

See what we got. The call to kevent (line 41) will return 1, indicating that 1 kevent has been placed in ke_retr. Remember that we have already registered SIGINT and sent it to ourselves. What is the 1 kevent that has been returned? It could be the signal we sent or error notification. It turns out to be reporting the error with the kevent we set in (line 38), so the SIGINT we sent must remain on the kqueue. Because an error occured, does this mean that our SIGHUP kevent has been discarded? No. Because there was space in the eventlist (our ke_retr) the error was reported and the rest of changelist processing continued. Note that the pending SIGINT wasnt returned even though there was space for it and the error on the eventlist.

EV_SET(&ke_set[0], 42, -10, EV_ADD, 0, 0, NULL);    /*< Line 56 */
EV_SET(&ke_set[1], SIGTERM, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);

i = kevent(kq, ke_set, 2, NULL, 0, NULL);

printf("second invalid call returned %d\n", i);     /*< Line 61 */

Here try to register an invalid and valid kevent, but this time with no eventlist. What will happen is kevent will return -1, and since there was no room on the eventlist to report the error, processing of changelist will stop and kevent will return immediately. This means the SIGTERM kevent has been discarded.

EV_SET(&ke_set[0], SIGQUIT, EVFILT_SIGNAL, EV_ADD, 0, 0, NULL);
EV_SET(&ke_set[1], 42, -10, EV_ADD, 0, 0, NULL); 

i = kevent(kq, ke_set, 2, NULL, 0, NULL);

printf("third invalid call returned %d\n", i);

Here we do the something similar to (lines 56 - 61) above, but this time the invalid kevent comes after the valid one. Here again kevent will return -1, as there is no space on the eventlist for the error, but this time, the SIGQUIT kevent will have been registered as it preceded the invalid one and so would have been processed already.

/* send signals again */
kill(getpid(), SIGINT);
kill(getpid(), SIGHUP);
kill(getpid(), SIGTERM);
kill(getpid(), SIGQUIT);

memset(ke_retr, 0x00, sizeof(ke_retr));

send each of the four signals we should have filters for, remember there is still the SIGINT kevent pending that we sent in line 36.

/* see whats waiting */
i = kevent(kq, NULL, 0, ke_retr, 4, NULL);
if (i == -1)
    err(1, "kevent second time!");

printf("kevent retrieve call returned %d\n", i);

for (j = 0; j < i; j++) 
    printf("received SIG%s %d times\n", 
        sys_signame[ke_retr[j].ident], ke_retr[j].data);

So lets see what we get. Here kevent will return 3, giving us SIGINT, SIGHUP and SIGQUIT, but no SIGTERM. We’ll also see that SIGINT has been received twice. As a side note it’s probably a bad idea to use sys_signame if your aiming for portability, but then again if you were aiming for that you wouldnt be using kqueues anyway.

Lets see it run:

$ ./kqerror
first invalid kevent call returned 1    #SIGHUP is set
error! errno 22: Invalid argument       #the error kevent
second invalid call returned -1         #SIGTERM fails to register
third invalid call returned -1          #SIGQUIT succeds in registering
kevent retrieve call returned 3         #see what we got
received SIGINT 2 times
received SIGHUP 1 times
received SIGQUIT 1 times
$

As you can see what happens depends on the order of the arguments. Obviously this is a contrived example, but i see two things you can do to detect errors. One is to pass in only one kevent at a time (which most of the other examples on this site do, just cause thats how things turned out), or pass in some space in the eventlist when registering kevents.