nginx 是如何處理過期事件的?

cn發表於2020-10-18

什麼是過期事件

對於不需要加入到 post 佇列 延後處理的事件,nginx 的事件都是通過 ngx_epoll_process_events 函式進行處理的

舉例:假如 epoll_wait 一次性返回 3 個事件,在第一個事件關閉了一個連線對應的正好是第三個事件的連線,第二個事件 accept 了一個連線,正好使用的是第二個事件的檔案描述符

如圖所示:

那麼如果僅僅判斷是否使用的同一個描述符或者描述符是否被置為 -1,就不能判斷是否是同一個連線

上面的這個問題,稱之為事件過期問題

nginx 是如何處理過期事件的?

nginx 中的指標的最後一位一定是 0 ,於是,nginx 就使用這最後一位用來表示是否過期

深入理解nginx中,第9章中有一句:利用指標的最後一位一定是0的特性。能解釋一下這個特性?

看下面程式碼中取出與判斷 instance 位的操作

nginx 會在每次 accept 一個連線的時候,將 instance 位取反,那麼只需要判斷 instance 位是否一直就能判斷事件是否過期了

// nginx-1.9.2/src/core/ngx_connection.c
// ngx_get_connection
    instance = rev->instance;

    ngx_memzero(rev, sizeof(ngx_event_t));
    ngx_memzero(wev, sizeof(ngx_event_t));

    rev->instance = !instance;
    wev->instance = !instance
// nginx-1.9.2/src/event/modules/ngx_epoll_module.c
// ngx_epoll_process_events
    //遍歷本次epoll_wait返回的所有事件
    for (i = 0; i < events; i++) { //和ngx_epoll_add_event配合使用
        /*
        對照著上面提到的ngx_epoll_add_event方法,可以看到ptr成員就是ngx_connection_t連線的地址,但最後1位有特殊含義,需要把它遮蔽掉
          */
        c = event_list[i].data.ptr; //通過這個確定是那個連線

        instance = (uintptr_t) c & 1; //將地址的最後一位取出來,用instance變數標識, 見ngx_epoll_add_event

        /*
          無論是32位還是64位機器,其地址的最後1位肯定是0,可以用下面這行語句把ngx_connection_t的地址還原到真正的地址值
          */ //注意這裡的c有可能是accept前的c,用於檢測是否客戶端發起tcp連線事件,accept返回成功後會重新建立一個ngx_connection_t,用來讀寫客戶端的資料
        c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);

        rev = c->read; //取出讀事件 //注意這裡的c有可能是accept前的c,用於檢測是否客戶端發起tcp連線事件,accept返回成功後會重新建立一個ngx_connection_t,用來讀寫客戶端的資料

        if (c->fd == -1 || rev->instance != instance) { //判斷這個讀事件是否為過期事件
          //當fd套接字描述符為-l或者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;
        }

        // 還有程式碼,但是不貼這麼多了
        ......
    }

那麼你可能也跟我有一樣的疑問,如果是下面兩種情形怎麼辦?


這樣 instance 位又成了 1,那 e 事件處理的豈不是 3 連線的事件了,這樣過期事件並沒有解決啊

首先,第二張圖片中的情況不可能存在,因為 epoll 中的事件是有順序的,c 事件必然是再 e 事件之後

那麼第一張圖片中的情況還是沒有解決過期事件啊

於是我就翻閱很多資料(主要靠百度)

看到有人遇到過這個疑問:

nginx中事件模型中instance變數的處理細節

如果覺得上面文章太長可以看我的講解:

意思呢就是

nginx accept 連線之後,會立刻將連線放到 post 延遲處理佇列中,不會出現 accept 之後立刻 close 的情況

於是呢,nginx 就完美的解決了事件過期的情況

參考資料

《深入理解 Nginx》

深入理解nginx中,第9章中有一句:利用指標的最後一位一定是0的特性。能解釋一下這個特性?

nginx中事件模型中instance變數的處理細節

github 專案:reading-code-of-nginx-1.9.2

相關文章