微信公衆號:鄭爾多斯
關注可瞭解更多的Nginx
知識。任何問題或建議,請公衆號留言;
關注公衆號,有趣有內涵的文章第一時間送達!php
本文分析了instance
的做用nginx
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 ngx_event_t
結構中的instance
變量是處理stale event
的核心,這裏值得一提的是,connection
鏈接池(實際是個數組)和event
數組(分read
和write
)。他們都是在初始化時就固定下來,以後不會動態增長和釋放,請求處理中只是簡單的取出和放回。並且有個細節就是,假設connection
數組的大小爲n
,那麼read event
數組和write event
數組的數量一樣是n,數量上同樣,每一個鏈接對應一個read
和write 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->log, 0,
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
?會不會出現這樣的狀況:
事件序列:A
… B
… B'
… C
,其中A,B,C跟以前討論的情形同樣,咱們如今很明確,B
中得到A
中釋放的鏈接時,會將instance
取反,這樣在C
中處理時,就能夠發現rev->instance != instance
,從而發現stale event
。那麼咱們假設B中處理時,又將該connection
釋放,在B'
中再次得到,一樣通過instance
取反,這時咱們會發現,instance
通過兩次取反時,就跟原來同樣了,這就不能經過fd == -1
與rev->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
,即B
和B'
確定不會在connection
上有任何攙和,在後續的處理中,對C
的影響也只是因爲B
或B'
從鏈接池中拿到了本應該屬於C
的connection
,從而致使fd
(被關閉)和instance
出現異常(被複用),因此如今看來,咱們擔憂的那個問題是多慮了。post
喜歡本文的朋友們,歡迎長按下圖關注訂閱號鄭爾多斯,更多精彩內容第一時間送達