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


喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達

鄭爾多斯
鄭爾多斯
相關文章
相關標籤/搜索