nginx事件 -- 第六篇 stale event

微信公众号:郑尔多斯
关注可了解更多的Nginx知识。任何问题或建议,请公众号留言;
关注公众号,有趣有内涵的文章第一时间送达!php

本文章原做者aweth0me,原文地址在这里点👇我啊,我作了简单的修改和整理

内容主题

本文分析了instance的做用nginx

什么是 stale event

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_wait()返回的文件描述符进行了cache,那么你必须动态监测咱们cache的文件描述符是否被关闭了。设想有这么一种状况,epoll_wait()一次返回了100个准备就绪的事件,而后再第#47号事件的处理函数中,关闭了第#13个事件。若是你把第#13号事件对应的结构体删除,而且把#13号事件对应的fd关闭,可是你缓存的event cache在处理到这个事件的时候就会比较郁闷了。一个解决的方案就是,在处理#47号事件的时候,使用epoll_ctl()关闭#13号事件对应的fd,而后把#13号事件对应的数据结构放到cleanup list链表中。微信

Nginx 如何处理

nginx ngx_event_t结构中的instance变量是处理stale event的核心,这里值得一提的是,connection链接池(实际是个数组)和event数组(分readwrite)。他们都是在初始化时就固定下来,以后不会动态增长和释放,请求处理中只是简单的取出和放回。并且有个细节就是,假设connection数组的大小为n,那么read event数组和write event数组的数量一样是n,数量上同样,每一个链接对应一个readwrite event结构,在连接被回收的时候,他们也就不能使用了。
咱们看看链接池取出和放回的动做:
先看放回,一个请求处理结束后,会经过ngx_free_connection将其持有的链接结构还回链接池,动做很简单:数据结构

1c->data = ngx_cycle->free_connections;
2ngx_cycle->free_connections = c;
3ngx_cycle->free_connection_n++;
复制代码

而后看下面的代码:app

 1/*
2这里要主要到的是,c结构体中并无清空,各个成员值还在(除了fd被置为-1外),那么新请求在从链接池里拿链接时,得到的结构都仍是没用清空的垃圾数据,咱们看取的时候的细节:
3*/

4
5// 此时的c含有没用的“垃圾”数据
6c = ngx_cycle->free_connections;
7......
8// rev和wev也基本上“垃圾”数据,既然是垃圾,那么取他们还有什么用?其实还有点利用价值。。
9rev = c->read;
10wev = c->write;
11
12// 如今才清空c,由于后面要用了,确定不能有非数据存在,从这里咱们也多少能够看得出,nginx
13// 为何在还的时候不清,我认为有两点:尽快还,让请求有链接可用;延迟清理,直到必须清理时。
14// 总的来讲仍是为了效率。
15ngx_memzero(c, sizeof(ngx_connection_t));
16
17c->read = rev;
18c->write = wev;
19c->fd = s;
20
21// 原有event中的instance变量
22instance = rev->instance;
23
24// 这里清空event结构
25ngx_memzero(rev, sizeof(ngx_event_t));
26ngx_memzero(wev, sizeof(ngx_event_t));
27
28// 新的event中的instance在原来的基础上取反。意思是,该event被重用了。由于在请求处理完
29// 以前,instance都不会被改动,并且当前的instance也会放到epoll的附加信息中,即请求中event
30// 中的instance跟从epoll里获得的instance是相同的,不一样则是异常了,须要处理。
31rev->instance = !instance;
32wev->instance = !instance;
复制代码

如今咱们实际的问题:ngx_epoll_process_eventside

 1// 当前epoll上报的事件挨着处理,有前后。
2    for (i = 0; i < events; i++) {
3        c = event_list[i].data.ptr;
4
5        // 难道epoll中附加的instance,这个instance是在刚获取链接池时已经设置的,通常是不会变化的。
6        instance = (uintptr_t) c & 1;
7        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
8
9        // 处理可读事件
10        rev = c->read;
11        /*
12          fd在当前处理时变成-1,意味着在以前的事件处理时,把当前请求关闭了,
13          即close fd而且当前事件对应的链接已被还回链接池,此时该次事件就不该该处理了,做废掉。
14          其次,若是fd > 0,那么是否本次事件就能够正常处理,就能够认为是一个合法的呢?答案是否认的。
15          这里咱们给出一个情景:
16          当前的事件序列是: A ... B ... C ...
17          其中A,B,C是本次epoll上报的其中一些事件,可是他们此时却相互牵扯:
18          A事件是向客户端写的事件,B事件是新链接到来,C事件是A事件中请求创建的upstream链接,此时须要读源数据,
19          而后A事件处理时,因为种种缘由将C中upstream的链接关闭了(好比客户端关闭,此时须要同时关闭掉取源链接),天然
20          C事件中请求对应的链接也被还到链接池(注意,客户端链接与upstream链接使用同一链接池),
21          而B事件中的请求到来,获取链接池时,恰好拿到了以前C中upstream还回来的链接结构,当前须要处理C事件的时候,
22          c->fd != -1,由于该链接被B事件拿去接收请求了,而rev->instance在B使用时,已经将其值取反了,因此此时C事件epoll中
23          携带的instance就不等于rev->instance了,所以咱们也就识别出该stale event,跳过不处理了。
24         */

25        if (c->fd == -1 || rev->instance != instance) {
26
27            /*
28             * the stale event from a file descriptor
29             * that was just closed in this iteration
30             */

31
32            ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log0,
33                           "epoll: stale event %p", c);
34            continue;
35        }
36
37        /*
38          咱们看到在write事件处理时,没用相关的处理。事实上这里是有bug的,在比较新的nginx版本里才被修复。
39          国内nginx大牛agent_zh,最先发现了这个bug,在nginx forum上有Igor和他就这一问题的讨论:
40          http://forum.nginx.org/read.php?29,217919,218752         
41         */

42
43        ......
44    }
复制代码

补充:
为何简单的将instance取反,就能够有效的验证该事件是不是stale event?会不会出现这样的状况:
事件序列:ABB'C,其中A,B,C跟以前讨论的情形同样,咱们如今很明确,B中得到A中释放的链接时,会将instance取反,这样在C中处理时,就能够发现rev->instance != instance,从而发现stale event。那么咱们假设B中处理时,又将该connection释放,在B'中再次得到,一样通过instance取反,这时咱们会发现,instance通过两次取反时,就跟原来同样了,这就不能经过fd == -1rev->instance != instance的验证,所以会当作正常事件来处理,后果很严重!
不知道看到这里的有没有跟我有一样的想法同窗,其实这里面有些细节没有被抖出来,实际上,这里是不会有问题的,缘由以下:
新链接经过accept来得到,即函数ngx_event_accept。在这个函数中会ngx_get_connection,从而拿到一个链接,而后紧接着初始化这个链接,即调用ngx_http_init_connection,在这个函数中一般是会这次事件挂到post_event链上去:函数

1if (ngx_use_accept_mutex) {
2    ngx_post_event(rev, &ngx_posted_events);
3    return;
4}
复制代码

而后继续accept或者处理其余事件。而一个进程能够进行accept,必然是拿到了进程间的accept锁。凡是进程拿到accept锁,那么它就要尽快的处理事务,并释放锁,以让其余进程能够accept,尽快处理的办法就是将epoll这次上报的事件,挂到响应的链表或队列上,等释放accept锁以后在本身慢慢处理。因此从epoll_wait返回到外层,才会对post的这些事件来作处理。在正式处理以前,每一个新建的链接都有本身的connection,即BB'确定不会在connection上有任何搀和,在后续的处理中,对C的影响也只是因为BB'从链接池中拿到了本应该属于Cconnection,从而致使fd(被关闭)和instance出现异常(被复用),因此如今看来,咱们担忧的那个问题是多虑了。post


喜欢本文的朋友们,欢迎长按下图关注订阅号郑尔多斯,更多精彩内容第一时间送达

郑尔多斯
郑尔多斯
相关文章
相关标签/搜索