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处理stale事件机制分析

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

nginx教程:nginx 中处理 stale event

http://www.linuxde.net/2011/12/3741.html man 7 epoll会发现这个东西,就是使用epoll中会遇到的问题: ...

nginx 源码学习笔记(二十一)—— event 模块(二) ——事件驱动核心ngx_process_events_and_timers

首先继续回忆下,之前子线程执行操作里面有一个未涉及的内容ngx_process_events_and_timers,今天我们就来研究下这个函数。 本篇文章来自于:http://blog.csdn.n...

nginx 源码学习笔记(二十二)—— event 模块(三) ——epoll模块

上一节我们讲到了事件驱动的模块,它把我们引入epoll模块,今天我们主要学习下nginx如何使用epoll完成时间驱动,实现高并发;这里不详细讲解epoll原理,如果有机会再做一次单独的epoll的学...

nginx 源码学习笔记(二十)—— event 模块(一) ——初始化

读完之前的学习笔记,相信已经对nginx的启动流程有了一定的认识,从这一节起我们想深入各个模块,学习各个模块的内的主要操作。 本文来自于:http://blog.csdn.net/lengzij...

bigchaindb源码分析(八)——stale

本节我们来分析stale进程,stale进程也是一个pipeline,其作用在于处理联盟中有被分配节点由于某些原因没有处理bakclog中的事务的问题,主要做法是对该事务的被分配节点进行重新分配# b...
  • lwyeluo
  • lwyeluo
  • 2017年07月16日 17:09
  • 185

nginx 源码学习笔记(二十三)—— event 模块(四) ——timer红黑树

在二十一节中,提到过调用ngx_eventfind_timer()获取timer,然后传递给epoll模块,做等待时间,今天我们主要讲解下这个方法。 本文来自于:http://blog.csdn.n...

nginx 源码学习笔记(二十)—— event 模块(一) ——初始化

读完之前的学习笔记,相信已经对nginx的启动流程有了一定的认识,从这一节起我们想深入各个模块,学习各个模块的内的主要操作。 本文来自于:http://blog.csdn.net/lengzijia...

Redis2.2.2源码学习——Server&Client链接的建立以及相关Event

在Server端和Client的通行是以文件事件进行管理的。Redis在initServer中就创建用于 监听client连接请求的ipfd,并在该文件符上建立文件事件,用于处理客户端的连接请求。而且...
  • ordeder
  • ordeder
  • 2013年11月01日 12:10
  • 2533

nginx源码分析--event事件驱动初始化

1.在nginx.c中设置每个核心模块的index ngx_max_module = 0; for (i = 0; ngx_modules[i]; i++) { ngx...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:nginx 源码学习——处理stale event
举报原因:
原因补充:

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