Nginx accept锁分析

nginx中accept锁主要是为了防止一个新连接的accept事件导致多个进程被唤醒的问题发生。

nginx中的进程只有抢占到accept锁时才会将监听的fd放入epoll中,这样同一时间只有一个进程可以接受新连接的accept事件。工作进程抢占到accept锁后,将监听端口的fd放入epoll中,再调用epoll_wait()获取新的事件。

如果调用epoll_wait获取到新的事件后立即处理这些事件,可能会导致read事件处理时间过长,其他进程无法获取新的连接的问题。为了避免这种情况的发生,nginx将read事件延后处理。维护两个队列:ngx_posted_accept_events存放accept事件,ngx_posted_events队列存放读事件。如果进程获取到accept锁,先处理ngx_posted_accept_events队列,再即释放accept锁,最后处理ngx_posted_events队列。

这一部分的逻辑在nginx的ngx_event.c:ngx_process_events_and_timers()函数中:

在epoll_wait之前先抢占accept锁,如果抢占成功,则在中flag增加NGX_POST_EVENTS选项,传入ngx_process_event函数。

    // 抢占accept锁
    if (ngx_use_accept_mutex) {
        if (ngx_accept_disabled > 0) {
            ngx_accept_disabled--;

        } else {
            // 获取锁错误
            if (ngx_trylock_accept_mutex(cycle) == NGX_ERROR) {
                return;
            }

            if (ngx_accept_mutex_held) {
                flags |= NGX_POST_EVENTS;

            } else {
                // 抢占锁失败,设置延时
                if (timer == NGX_TIMER_INFINITE
                    || timer > ngx_accept_mutex_delay)
                {
                    timer = ngx_accept_mutex_delay;
                }
            }
        }
    }

ngx_trylock_accept_mutex函数在加锁时检查是否有未处理的accept事件,和epoll是否可用,同时调用ngx_shmtx_trylock加锁。如果获得锁将ngx_accept_mutex_held 置为1

ngx_int_t
ngx_trylock_accept_mutex(ngx_cycle_t *cycle)
{
    if (ngx_shmtx_trylock(&ngx_accept_mutex)) {

        ngx_log_debug0(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                       "accept mutex locked");

        if (ngx_accept_mutex_held && ngx_accept_events == 0) {
            return NGX_OK;
        }

        if (ngx_enable_accept_events(cycle) == NGX_ERROR) {
            ngx_shmtx_unlock(&ngx_accept_mutex);
            return NGX_ERROR;
        }

        ngx_accept_events = 0;
        ngx_accept_mutex_held = 1;

        return NGX_OK;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
                   "accept mutex lock failed: %ui", ngx_accept_mutex_held);

    if (ngx_accept_mutex_held) {
        if (ngx_disable_accept_events(cycle, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }

        ngx_accept_mutex_held = 0;
    }

    return NGX_OK;
}

ngx_shmtx_trylock函数通过原子变量进行加锁:

ngx_uint_t
ngx_shmtx_trylock(ngx_shmtx_t *mtx)
{
    return (*mtx->lock == 0 && ngx_atomic_cmp_set(mtx->lock, 0, ngx_pid));
}

尝试获取accept锁后,进入事件处理函数ngx_process_events:

(void) ngx_process_events(cycle, timer, flags);

ngx_process_events是一个函数指针,使用epoll时,指向:ngx_epoll_process_events函数。首先,调用epoll_wait()获取evs,再对事件进行处理:

// epoll_wait 
events = epoll_wait(ep, event_list, (int) nevents, timer);

err = (events == -1) ? ngx_errno : 0;

拿到accept锁,即flags=NGX_POST_EVENTS时:不会直接处理事件,将accept事件放到ngx_posted_accept_events队列,read事件放到ngx_posted_events队列。如果没有拿到accept锁,则处理的全部是read事件,直接进行回调函数处理。

没获取到accept锁时:如果处理的事件是可写事件,则将事件的ready置为1,供后续流程使用。如果获处理的事件是可读事件,直接调用写事件的回调函数(handler)。

  1     // 处理epoll_wait()返回的事件
  2     for (i = 0; i < events; i++) {
  3         c = event_list[i].data.ptr;
  4 
  5         instance = (uintptr_t) c & 1;
  6         c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~1);
  7 
  8         rev = c->read;
  9 
 10         // 事件过期
 11         if (c->fd == -1 || rev->instance != instance) {
 12 
 13             /*
 14              * the stale event from a file descriptor
 15              * that was just closed in this iteration
 16              */
 17 
 18             ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 19                            "epoll: stale event %p", c);
 20             continue;
 21         }
 22 
 23         revents = event_list[i].events;
 24 
 25         ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 26                        "epoll: fd:%d ev:%04XD d:%p",
 27                        c->fd, revents, event_list[i].data.ptr);
 28 
 29         // 错误事件
 30         if (revents & (EPOLLERR|EPOLLHUP)) {
 31             ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 32                            "epoll_wait() error on fd:%d ev:%04XD",
 33                            c->fd, revents);
 34 
 35             /*
 36              * if the error events were returned, add EPOLLIN and EPOLLOUT
 37              * to handle the events at least in one active handler
 38              */
 39 
 40             revents |= EPOLLIN|EPOLLOUT;
 41         }
 42 
 43 #if 0
 44         if (revents & ~(EPOLLIN|EPOLLOUT|EPOLLERR|EPOLLHUP)) {
 45             ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
 46                           "strange epoll_wait() events fd:%d ev:%04XD",
 47                           c->fd, revents);
 48         }
 49 #endif
 50 
 51         // 读事件活跃
 52         if ((revents & EPOLLIN) && rev->active) {
 53 
 54 #if (NGX_HAVE_EPOLLRDHUP)
 55             if (revents & EPOLLRDHUP) {
 56                 rev->pending_eof = 1;
 57             }
 58 #endif
 59 
 60             rev->ready = 1;
 61             rev->available = -1;
 62             // 延后处理,存放在队列中
 63             if (flags & NGX_POST_EVENTS) {
 64                 queue = rev->accept ? &ngx_posted_accept_events
 65                                     : &ngx_posted_events;
 66 
 67                 ngx_post_event(rev, queue);
 68 
 69             } else {
 70                 // 调用read回调 ngx_http_wait_request_handler
 71                 // 第一次调用 ngx_event_accept
 72                 rev->handler(rev);
 73             }
 74         }
 75 
 76         wev = c->write;
 77 
 78         // 写事件活跃
 79         if ((revents & EPOLLOUT) && wev->active) {
 80 
 81             if (c->fd == -1 || wev->instance != instance) {
 82 
 83                 /*
 84                  * the stale event from a file descriptor
 85                  * that was just closed in this iteration
 86                  */
 87 
 88                 ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0,
 89                                "epoll: stale event %p", c);
 90                 continue;
 91             }
 92 
 93             wev->ready = 1;
 94 #if (NGX_THREADS)
 95             wev->complete = 1;
 96 #endif
 97 
 98             // 延后处理,添加到队列
 99             if (flags & NGX_POST_EVENTS) {
100                 ngx_post_event(wev, &ngx_posted_events);
101 
102             } else {
103                 // 调用写事件的回调函数
104                 wev->handler(wev);
105             }
106         }
107     }

 

posted @ 2021-04-19 18:34  wa小怪兽  阅读(321)  评论(0编辑  收藏  举报