Question

I'm learning to use epoll, and I wrote the following example

#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>

int main() {
    int epfd;
    struct epoll_event ev;
    struct epoll_event ret;
    char buf[200];
    int n,k,t;

    epfd = epoll_create(100);
    assert(0 ==
            fcntl(0, F_SETFL, fcntl(0, F_GETFL) | O_NONBLOCK)
          );

    ev.data.fd = 0;
    ev.events = EPOLLIN  | EPOLLET;

    if(epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &ev) != 0)
        perror("epoll_ctl");


    while((n = epoll_wait(epfd, &ret, 1, -1)) > 0) {
        printf("tick!\n");

        if(ret.data.fd == 0) {
            k=0;
            while((t=read(0, buf, 100)) > 0) {
                k+=t;
            }   

            if(k == 0) {
                close(0);
                printf("stdin done\n");
            }
        }   
    }

    perror("epoll");
    return 0;
}

If you try running it in the terminal it won't work properly since fds 0, 1 and 2 all point to same open file, so close(0) won't remove stdin from the epoll set. You can get around this by doing "cat | ./a.out". Dirty trick, I know, but setting up a small example with named pipes or sockets would be more complicated.

Now, everything works and the file is removed from the epoll set, but then the next epoll_wait call blocks permanently since it's on an empty set! So I would need to detect if the epoll file descriptor (epfd) is an empty epoll set.

How can I get around this? (in a general manner, not just calling exit when stdin is done) Thanks!

Was it helpful?

Solution

Basically, if you're using epoll "correctly", then you should never have a situation of an unexpectedly empty epoll set. You should know when there is more to do or not. Well, or that's the theory at least. Let me review it:

You are using EPOLLET here (which is, imho, the right think in general). It means that the file descriptor 0 is removed from the epoll when it is returned in &ret. At this point you should handle it by reading some amount of data from 0, as you do, but then "re-arming" it by adding again file descriptor 0 into the epoll (unless it was closed of course). For an example of how this is supposed to work, remove the inner loop and just do:

k = read(0, buf, 100);

reading a maximum of 100 bytes. The idea is that if you pipe a file bigger than that, it should go several times through the whole loop. In order to make this work, if k > 0, after you handle the k bytes, you need to call epoll_ctl(..EPOLL_CTL_ADD..) again.

Note an annoying detail: it's possible occasionally that the read() returns 0 bytes without meaning the file or socket is at the end. Check if errno == EAGAIN || errno == EWOULDBLOCK. To detect that case, and then epoll_ctl(..EPOLL_CTL_ADD..) again.

OTHER TIPS

The epoll set will be empty when you've removed everything that was added. As far as I know, you can't introspect the epoll set to find out whether there are any file descriptors present. So, it's up to you to determine when the epoll set becomes empty as outlined in Armin's answer.

Since you haven't explained what you expect from your program, I'll take a guess that you expect it exit when stdin is closed, because doing a close(0) will potentially cause file descriptor 0 to be removed from the epoll set. However, the code as listed is flawed. If you continue to wait on an epoll set that doesn't contain any file descriptors (whether removed automatically or by using EPOLL_CTL_DEL), the epoll_wait will wait forever.

The following code shows this nicely.

#include <errno.h>
#include <stdio.h>
#include <sys/epoll.h>

int main() {
    int epfd;
    int n;
    struct epoll_event ret;

    epfd = epoll_create(100);

    while((n = epoll_wait(epfd, &ret, 1, -1)) > 0) {
        /* Never gets here. */
        printf("tick!\n");
    }

    return 0;
}

The epoll set doesn't contain any file descriptors, so the epoll_wait wait forever. If you happened to have a file connected to stdin in your program and no other file descriptor in your program was connected to stdin, the close(0) would have removed fd 0 from the set, the epoll set becomes empty, and the next epoll_wait waits forever.

In general, you manage file descriptors in the epoll set yourself, not rely on close calls to automatically remove your file descriptor from the set. It's up to you to decide whether to continue waiting on the epoll set after you've done the close(0).

I'd also suggest that you change the structure of your program to epoll_wait after the read. This guarantees that you'll obtain any data that may have arrived on stdin before your first call to epoll_wait.

Also, be careful with code like this:

k=0;
while((t=read(0, buf, 100)) > 0) {
    k+=t;
}   
if(k == 0) {
    close(0);
    printf("stdin done\n");
}

If you assume that the read in the loop consecutively returns 100 followed by 0 indicating some data plus an end of file, the close(0) will not be called. The program will loop and wait forever again on epoll_wait. Best to check the result of each read specifically for end-of-file and errors.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top