Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kqueue returning both EVFILT_READ and EVFILT_WRITE but I installed separate (ident,filt) pairs

Tags:

c

kqueue

I'm running into an issue that I'm not sure if is expected behavior from kqueue or if it's something I'm doing wrong.

I need to install separate events with kqueue for a single socket file descriptor. The separate events are one for (sockfd,EVFILT_READ) and (sockfd,EVFILT_WRITE). However when I pull events out of kqueue later I get two events from kqueue, but both events contain (event.filter & EVFILT_READ) and (event.filter & EVFILT_WRITE).

I need them to be separate otherwise I don't know which event actually is ready, read or write?

Here's some sample code I put together to illustrate the issue. Please keep in mind this is just test code to figure out why I'm getting both filter flags in each event.

int main(int argc, char ** argv) {
    //listening_sockfd = setup listening socket

    //setup kqueue and vars
    int kq = kqueue();
    int res = 0;
    int bufres = 0;
    struct timespec ts = {0};
    struct timespec * tsp = &ts;
    struct kevent ke2[2];

    //install read for listening socket
    struct kevent ke1;
    bzero(&ke1,sizeof(ke1));
    ke1.ident = listening_sockfd;
    ke1.flags = EV_ADD;
    ke1.filter = EVFILT_READ;
    res = kevent(kq,&ke1,1,NULL,0,tsp);

    while(1) {
        new_client:

        //wait for a client to connect
        res = kevent(kq,NULL,0,&ke1,1,NULL);
        if(res < 0) printf("%s\n", strerror(errno));

        if(res > 0) {
            //accept the client
            int clientfd = accept(skinfo.sockfd,NULL,NULL);

            //install read events for client
            bzero(&ke1,sizeof(ke1));
            ke1.ident = clientfd;
            ke1.flags = EV_ADD;
            ke1.filter = EVFILT_READ;
            res = kevent(kq,&ke1,1,NULL,0,tsp);
            if(res < 0) printf("%s\n",strerror(errno));

            while(1) {

                //wait for readable content from the client
                bzero(&ke1,sizeof(ke1));
                res = kevent(kq,NULL,0,&ke1,1,NULL);
                if(res < 0) printf("%s\n",strerror(errno));

                if(res > 0) {

                    //check for client disconnect
                    if(ke1.flags & EV_EOF) {
                        close(clientfd);
                        goto new_client;
                    }

                    //now install write events for client
                    bzero(&ke1,sizeof(ke1));
                    ke1.ident = clientfd;
                    ke1.flags = EV_ADD;
                    ke1.filter = EVFILT_WRITE;
                    res = kevent(kq,&ke1,1,NULL,0,tsp);
                    if(res < 0) printf("%s\n",strerror(errno));

                    //now wait for it to be writable - this will return
                    //immediately because the socket is writable.
                    res = kevent(kq,NULL,0,ke2,2,NULL);
                    if(res < 0) printf("%s\n",strerror(errno));

                    if(res >= 0) {
                        char buf[128];
                        printf("res: %i\n",res);

                        //we have two events from kqueue because I installed
                        //two (ident,filter) pairs.
                        int i = 0;
                        for(i; i<2; i++) {
                            printf("ident: %i\n",ke2[i].ident);
                            printf("filter[%i] %lu\n",i,ke2[i].filter);
                            printf("data: %lu\n",ke2[i].data);

                            //QUESTION: Why does EVFILT_READ && EVFILT_WRITE come
                            //back in the same event when I installed two seperate
                            //(ident,filter) pairs?
                            if(ke2[i].filter & EVFILT_READ) printf("EVFILT_READ\n");
                            if(ke2[i].filter & EVFILT_WRITE) printf("EVFILT_WRITE\n");

                            if(ke2[i].filter & EVFILT_READ) {
                                printf("readable!\n");
                                bufres = read(clientfd,buf,128);
                            }

                            if(ke2[i].filter & EVFILT_WRITE) {
                                printf("writable\n");

                                //shut off write events to stop infinite loop
                                //because the socket is writable
                                bzero(&ke1,sizeof(ke1));
                                ke1.ident = clientfd;
                                ke1.flags = EV_DELETE;
                                ke1.filter = EVFILT_WRITE;
                                res = kevent(kq,&ke1,1,NULL,0,tsp);

                                write(clientfd,buf,bufres);
                            }
                        }
                    }
                }
            }
        }
    }
}

I haven't been able to figure this out. Why does each kqueue event contain both EVFILT_READ and EVFILT_WRITE when I installed separate events?

The real issue this is creating for me is that because these events always report EVFILT_READ, it has the side-effects of the code always thinking there's a read available, but there's really not, so a call to read() will be unsuccessful and have other consequences. Note that this code doesn't show those consequences, this is just a sample of the issue I wrote so I figure this out and continue with my real code.

Any ideas?

PS Here's output from the printf:

res: 2
ident: 5
filter[0] 4294967295
data: 5
EVFILT_READ
EVFILT_WRITE
readable!
writable
ident: 5
filter[1] 4294967294
data: 146988
EVFILT_READ
EVFILT_WRITE
readable!
writable
like image 713
gngrwzrd Avatar asked Dec 16 '22 20:12

gngrwzrd


2 Answers

AH! I think I figured it out. What kqueue documentation doesn't say is that event.filter doesn't contain bit flags. So I need to check for read/write with event.filter == EVFILT_READ, event.filter == EVFILT_WRITE.

like image 62
gngrwzrd Avatar answered Dec 28 '22 08:12

gngrwzrd


As you noticed, they are not bits to be tested.

Consulting the header on OS X, we find

#define EVFILT_READ             (-1)
#define EVFILT_WRITE            (-2)

Since -1 is all 1-bits, x & EVFILT_READ will be true for any nonzero x, and x & EVFILT_WRITE will be true for x not equal to zero or one.

like image 26
Potatoswatter Avatar answered Dec 28 '22 10:12

Potatoswatter