epoll: difference between level triggered and edge triggered when EPOLLONESHOT specified

Viewed 1521

What's the difference between level triggered and edge triggered mode, when EPOLLONESHOT specified?

There's a similar question already here. The answer by "Crouching Kitten" doesn't seem to be right (and as I understand, the other answer doesn't answer my question).

I've tried the following:

  • server sends 2 bytes to a client, while client waits in epoll_wait
  • client returns from epoll_wait, then reads 1 byte.
  • client re-arms the event (because of EPOLLONESHOT)
  • client calls epoll_wait again. Here, for both cases (LT & ET), epoll_wait doesn't wait, but returns immediately (contrary to the answer by "Crouching Kitten")
  • client can read the second byte

Is there any difference between LT & ET, when EPOLLONESHOT specified?

3 Answers

I think the bottom line answer is "there is not difference".

Looking at the code, it seems that the fd remembers the last set bits before being disabled by the one-shot. It remembers it was one shot, and it remembers whether it was ET or not.

Which is futile, because the fd is disabled until modified, and the next call to EPOLL_CTL_MOD will erase all of that, and replace with whatever the new MOD says.

Having said that, I do not understand why anyone would want both EPOLLET and EPOLLONESHOT. To me, the whole point of EPOLLET is that, unders certain programming models (namely, microthreads), it follows the semantics perfcetly. This means that I can add the fd to the epoll at the very start, and then never have to perform another epoll related system call.

EPOLLONESHOT, on the other hand, is used by people who want to keep a very strict control over when the fd is watched and when it isn't. That, by definition, is the opposite of what EPOLLET is used for. I just don't think the two are conceptually compatible.

The other poster said "I do not understand why anyone would want both EPOLLET and EPOLLONESHOT." Actually, according to epoll(7), there is a use case for that:

Since even with edge-triggered epoll, multiple events can be generated upon receipt of multiple chunks of data, the caller has the option to specify the EPOLLONESHOT flag, to tell epoll to disable the associated file descriptor after the receipt of an event with epoll_wait(2).

The key point is that whether EPOLL will treat the combination of EPOLLET | EPOLLONESHOT and EPOLLLT | EPOLLONESHOT as special case. As I known, it is not. EPOLL just care them seperately. To EPOLLET and EPOLLLT, the different kindly only is in function ep_send_events, if the EPOLLET is set, then the function will call list_add_tail to add the epitem into the ready list in epoll_fd/eventepoll object. To the EPOLLONESHOT, the role is to disable the fd. So I think the different between them is the different between ET and LT. You can check the result using below codes I think

// server.cc
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

void addfd(int epollfd, int fd, bool oneshot)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    if(oneshot)
        event.events |= EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    setnonblocking(fd);
}
// reset the fd with EPOLLONESHOT
void reset_oneshot(int epollfd, int fd)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

int main(int argc, char** argv)
{
    if(argc <= 2)
    {
        printf("usage: %s ip_address port_number\n", basename(argv[0]));
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi(argv[2]);

    int ret = 0;
    struct sockaddr_in address;
    bzero(&address, sizeof(address));
    address.sin_family = AF_INET;
    inet_pton(AF_INET, ip, &address.sin_addr);
    address.sin_port = htons(port);

    int listenfd = socket(PF_INET, SOCK_STREAM, 0);
    assert(listenfd >= 0);
    ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
    assert(ret != -1);

    ret = listen(listenfd, 5);
    assert(ret != -1);

    epoll_event events[MAX_EVENT_NUMBER];
    int epollfd = epoll_create(5);
    addfd(epollfd, listenfd, false);
    while(1)
    {
        printf("next loop: -----------------------------");
        int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
        if(ret < 0)
        {
            printf("epoll failure\n");
            break;
        }
        for(int i = 0; i < ret; i++)
        {
            int sockfd = events[i].data.fd;
            if(sockfd == listenfd)
            {
                printf("into listenfd part\n");
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof(client_address);
                int connfd = accept(listenfd, (struct sockaddr*)&client_address,
                     &client_addrlength);
                printf("receive connfd: %d\n", connfd);
                addfd(epollfd, connfd, true);
                // reset_oneshot(epollfd, listenfd);
            }
            else if(events[i].events & EPOLLIN)
            {
                printf("into linkedfd part\n");
                printf("start new thread to receive data on fd: %d\n", sockfd);
                char buf[2];
                memset(buf, '\0', 2);
                // just read one byte, and reset the fd with EPOLLONESHOT, check whether still EPOLLIN event
                int ret = recv(sockfd, buf, 2 - 1, 0);

                if(ret == 0)
                {
                    close(sockfd);
                    printf("foreigner closed the connection\n");
                    break;
                }
                else if(ret < 0)
                {
                    if(errno == EAGAIN)
                    {
                        printf("wait to the client send the new data, check the oneshot memchnism\n");
                        sleep(10);
                        reset_oneshot(epollfd, sockfd);
                        printf("read later\n");
                        break;
                    }
                }
                else {
                    printf("receive the content: %s\n", buf);
                    reset_oneshot(epollfd, sockfd);
                    printf("reset the oneshot successfully\n");
                }
            }
            else 
                printf("something unknown happend\n");
        }
        sleep(1);
    }
    close(listenfd);
    return 0;
}

the Client is

from socket import *
import sys
import time

long_string = b"this is a long content which need two time to fetch"

def sendOneTimeThenSleepAndClose(ip, port):
    s = socket(AF_INET, SOCK_STREAM);
    a = s.connect((ip, int(port)));
    print("connect success: {}".format(a));
    data = s.send(b"this is test");
    print("send successfuly");
    time.sleep(50);
    s.close();
sendOneTimeThenSleepAndClose('127.0.0.1', 9999)
Related