nginx 源码学习——处理stale event

原创 2015年07月06日 18:06:45

处理stale event

添加到epoll后,worker进程会进入ngx_epoll_process_events函数,epoll_wait等待客户端发起连接请求,触发事件。而在对读写事件进行操作时,都会出现一个instance,这个变量到底是何用意?
我们首先看看man手册关于epoll的注解:

*If there is a large amount of I/O space, it is possible that by trying to drain it the other files will not get processed causing
starvation.
This is not specific to epoll. The solution is to maintain a ready list and mark the file descriptor as ready in its associated data structure, thereby allowing the application to remember which files need to be processed but still round robin amongst all the ready files. This also supports ignoring subsequent events you receive for fd’s that are already ready.
o If using an event cache…
If you use an event cache or store all the fd’s returned from epoll_wait(2), then make sure to provide a way to mark its closure dynamically (ie- caused by a previous event’s processing). Suppose you receive 100 events from epoll_wait(2), and in event #47 a condition causes event #13 to be closed. If you remove the structure and close() the fd for event #13, then your event cache might still say there are events waiting for that fd causing confusion.
One solution for this is to call, during the processing of event 47, epoll_ctl(EPOLL_CTL_DEL) to delete fd 13 and close(), then mark its associated data structure as removed and link it to a cleanup list. If you find another event for fd 13 in your batch processing, you will discover the fd had been previously removed and there will be no confusion.*

大概意思如下:
当epoll中存在大量的监听fd,可能会出现因为处理别的fd监听事件阻塞、超时而导致后面的fd饿死的情况。
解决这个问题的方法是可以用一个list标记所有需要被触发的fd,并且循环调用。同时记录已经失效的fd,在下次调用时不再触发。
如果使用事件缓存或者保存epoll_wait返回的所有待触发事件,然后动态确认是否有事件已被关闭。假设有100个事件,47号事件在某个情况下回关闭13号事件,如果你从缓存中删除了结构体,close了fd,但是如果还有别的事件也需要用到13号事件的话,事件缓存还是可能会触发13号事件。这会引起混乱。
解决这个问题的一个方法就是在47号事件被处理的时候调用epoll_ctl(EPOLL_CTL_DEL),从epoll队列中删除13号事件。同时从事件缓存中删除,放入空闲列表中。如果你再发现有别的事件需要用到13号事件的话,你就会发现13号事件已经被删除了,不会有混乱。

而nginx使用了更巧妙的方法来解决这个问题,那就是ngx_event_t 里的instance变量。

typedef union epoll_data {
    void         *ptr;
    int           fd;
    uint32_t      u32;
    uint64_t      u64;
} epoll_data_t;

struct epoll_event {
    uint32_t      events;
    epoll_data_t  data;
};

ngx_connection_t *
ngx_get_connection(ngx_socket_t s, ngx_log_t *log)
{
    ngx_uint_t         instance;
    ngx_event_t       *rev, *wev;
    ngx_connection_t  *c;

    instance = rev->instance;

    ngx_memzero(rev, sizeof(ngx_event_t));
    ngx_memzero(wev, sizeof(ngx_event_t));
    //在获取空闲connection对象的时候,将instance对象赋值为!instance
    rev->instance = !instance;
    wev->instance = !instance;
}

ngx_event_t 结构体中定义了instance,占用1bit位。这个变量从free connections取出时,赋值!x。由于系统的指针对齐,所以末尾最后一位一般为0。nginx将读写事件的标志位instance存储于最后一位,这样就不需要反复调用epoll_ctl(EPOLL_CTL_DEL),只需要通过标志位就能反映出一个读写事件是否继续可用,性能更加高效。

static ngx_int_t
ngx_epoll_add_connection(ngx_connection_t *c)
{
    struct epoll_event  ee;

    ee.events = EPOLLIN|EPOLLOUT|EPOLLET|EPOLLRDHUP;
    ee.data.ptr = (void *) ((uintptr_t) c | c->read->instance);

    ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0,
                   "epoll add connection: fd:%d ev:%08XD", c->fd, ee.events);

    if (epoll_ctl(ep, EPOLL_CTL_ADD, c->fd, &ee) == -1) {
        ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno,
                      "epoll_ctl(EPOLL_CTL_ADD, %d) failed", c->fd);
        return NGX_ERROR;
    }

    c->read->active = 1;
    c->write->active = 1;

    return NGX_OK;
}

事件被触发

ngx_epoll_process_events是每个worker进程的处理函数,用来等待事件响应,并且判断读写事件是否已经无效。

ngx_epoll_process_events

    events = epoll_wait(ep, event_list, (int) nevents, timer);

 for (i = 0; i < events; i++) {
        c = event_list[i].data.ptr;
        //从data.ptr指针中取出instance变量
        instance = (uintptr_t) c & 1;
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        rev = c->read;
        //判断该事件是否已经无效,无效则跳过,不进行下面操作
        if (c->fd == -1 || rev->instance != instance) {

            /*
             * the stale event from a file descriptor
             * that was just closed in this iteration
             */

            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                           "epoll: stale event %p", c);
            continue;
        }

nginx教程:nginx 中处理 stale event

http://www.linuxde.net/2011/12/3741.html man 7 epoll会发现这个东西,就是使用epoll中会遇到的问题: ...
  • hintonic
  • hintonic
  • 2012年01月20日 14:33
  • 570

Nginx中如何处理stale event

Nginx中如何处理stale event 1. 什么是stale event             其实来讲,stale event译为陈旧事件,也就是epoll中处理一批事件时,处理前面的...
  • chenglinhust
  • chenglinhust
  • 2013年07月23日 09:59
  • 1218

Nginx源码学习-从零开始(1)

Nginx源码结构版本: nginx-1.11.7 源码目录结构 ├── auto #自动检测系统环境以及编译相关的脚本 │ ├── cc #关于编译...
  • lichangrui2009
  • lichangrui2009
  • 2016年12月28日 17:42
  • 859

Nginx处理stale事件机制分析

Nginx为提高效率采用描述符缓冲池(连接池)来处理tcp连接,一个连接对应一个读事件和一个写事件,nginx在启动的时候会创建好所用连接和事件,当事件来的时候不用再创建,然而连接池的使用却存在sta...
  • zcc_0015
  • zcc_0015
  • 2014年07月09日 23:53
  • 1091

nginx event 模块解析

typedef struct { ngx_str_t name; void *(*create_conf)(ngx_cycle_t *...
  • jackywgw
  • jackywgw
  • 2015年09月23日 10:45
  • 1465

nginx源码初读(0)--学习nginx必看的网站

http://lxr.nginx.org/source 就是这个网站,旁边有search功能,可以很方便的找到各种结构在哪里定义,在哪里引用使用过,从而更好的理解代码。 配合vim+ctags,太方便...
  • wuchunlai_2012
  • wuchunlai_2012
  • 2016年02月19日 14:44
  • 646

Nginx源码分析与实践---(一)编写一个简单的Http模块

在上一节中,我们通过修改配置文件,便能让nginx去访问我们写的html文件,并返回给浏览器。问题是:nginx是如何检测到我们写的配置项的?检测到后,nginx又是如何知道该进行什么操作的? 本节通...
  • ZX714311728
  • ZX714311728
  • 2017年03月15日 21:04
  • 535

Nginx的源码结构和模块初始化

前言:上一篇(http://blog.csdn.net/xlgen157387/article/details/49781487)已经介绍了Nginx的基本功能,也介绍了在Windows下的安装和简单...
  • u010870518
  • u010870518
  • 2015年11月18日 17:07
  • 7950

Nginx event核心模块之epoll模块详解(一)

Nginx event核心模块之epoll模块详解(一)        在介绍epoll之前,先介绍下两个重要的数据结构,一个是ngx_event_t,另外一个是ngx_connection_t。这...
  • onelight1997
  • onelight1997
  • 2012年12月07日 13:26
  • 4648

jQuery源码分析--event事件绑定(上)

上文提到,jquery的事件绑定有bind(),delegate()和one()以及live()方式。我用的jQuery2.1.3版本,live()已经被废弃了。bind(),delegate()和o...
  • u014787301
  • u014787301
  • 2016年05月13日 22:12
  • 8843
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:nginx 源码学习——处理stale event
举报原因:
原因补充:

(最多只允许输入30个字)