4

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!

shyam
  • 9,134
  • 4
  • 29
  • 44
Guido
  • 2,571
  • 25
  • 37
  • What exactly are you expecting when you say "won't work properly"? That the `epoll_wait` will fail after stdin is closed and cause the `while` loop to exit? – Austin Phillips Apr 22 '13 at 05:35
  • @AustinPhillips If you start the program as ./a.out. File descriptors 0, 1 and 2 all refer to the same open file (the terminal). So closing stdin (0) has no effect, since epoll works with open files and not fd's. Question 6 in `man epoll` explains this better. You can bypass this by doing `cat | ./a.out`, since now fd 0 is a pipe, and fd's 1 and 2 are the terminal. – Guido Apr 22 '13 at 22:09
  • You haven't explained what you expect. As A6 in the man page explains, the file descriptor will be removed from the epoll set when there are no more references to the 'file description'. If you `close(0)` you probably have no more interest in the program continuing. In that case, you can `EPOLL_CTL_DEL` fd 0 from the epoll set, decrement a counter somewhere which counts number of outstanding file descriptors in `epfd` and if it reaches 0, exit the program. – Austin Phillips Apr 22 '13 at 23:59
  • Side note: epoll would allow do add new fds from a thread, while another blocks in `epoll_wait()`, receiving any upcoming events for that new fd. So the already mentioned counter is only safe, if one has full/interlocked control over the epoll instance - less error prone but a bit slower would be an own fd set, which is synchronized when invoking `epoll_ctl()`. Such a set entry could also contain other related information, e.g. PIDs of child processes (for pipe fds), while `epoll_event`s contain a pointer or a key to such entries. – Sam Aug 09 '14 at 21:50

2 Answers2

0

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.

Armin Rigo
  • 12,048
  • 37
  • 48
  • "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." Sorry but this is wrong, you're confusing edge-triggered epoll will EPOLLONESHOT, no? – Guido Apr 18 '13 at 15:51
  • Oops, you're right. I was forgetting about EPOLLONESHOT and assuming it was implied by EPOLLET. Sorry about that; my description is for this case, and I gave it because I thought I saw errors in your code, whereas there are none (it's just a different approach). – Armin Rigo Apr 18 '13 at 20:47
0

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.

Austin Phillips
  • 15,228
  • 2
  • 51
  • 50