NGX对于不是立即成功的socket I/O,即返回WSAEINPROGRESS,EINPROGRESS,EWOULDBLOCK,EAGAIN,WSAEWOULDBLOCK这几个值的(其中WSAEINPROGRESS,EINPROGRESS属于非阻塞socket的返回值,另外3个则是阻塞模式下I/O模型的返回值)则会调用ngx_add_timer向一个全局红黑树里面添加一个定时器。
当初我在自己写一个IOCP框架的时候,也曾思考过这问题,怎样对IOCP上面的单I/O进行超时管理。是对每个I/O设置一个定时器吗,这样如果系统存在大量定时器,对服务器的压力会不会很大,下面看看NGX怎么解决这个问题。
NGX利用红黑树来组织那些等待处理并且需要关注其是否超时的事件对象。将红黑树节点作为ngx_event_t的值成员,而非指针成员,则通过红黑树找到最先到期的节点后,通过offsetof宏可以直接得到ngx_event_t本身.因此所有定时器都放在红黑树中.
nginx处理超时有2种方案。
(1)常规的定时检测机制:设置定时器,每过一定的时间就对红黑树管理的所有事件对象进行一次超时检测
(2)经历距离当前最快发生超时的事件对象的时间就进行一次超时检测。
在配置文件中,通过使用timer_resulotion 来设置使用常规的定时检测机制,如果没有设置timer_resulotion指令,那么Nginx使用2)中方案。
超时的初始化在ngx_event_process_init函数中进行,调用的是ngx_event_timer_init,代码如下
ngx_int_t
ngx_event_timer_init(ngx_log_t *log)
{
ngx_rbtree_init(&ngx_event_timer_rbtree, &ngx_event_timer_sentinel,
ngx_rbtree_insert_timer_value);
return NGX_OK;
}
如果设置了timer_resulotion,会创建一个固定的定时器,并进行初始化,调用sigaction设置信号处理函数ngx_timer_signal_handler,代码如下:
if (ngx_timer_resolution && !(ngx_event_flags & NGX_USE_TIMER_EVENT)) {
struct sigaction sa;
struct itimerval itv;
ngx_memzero(&sa, sizeof(struct sigaction));
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask);
if (sigaction(SIGALRM, &sa, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"sigaction(SIGALRM) failed");
return NGX_ERROR;
}
itv.it_interval.tv_sec = ngx_timer_resolution / 1000;
itv.it_interval.tv_usec = (ngx_timer_resolution % 1000) * 1000;
itv.it_value.tv_sec = ngx_timer_resolution / 1000;
itv.it_value.tv_usec = (ngx_timer_resolution % 1000 ) * 1000;
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno,
"setitimer() failed");
}
}
初始化就这样了,超时的处理过程主要在ngx_process_events_and_timers函数里面,此函数是一个重要处理节点,在前面的系列中多次提到。这个函数首先会决定超时处理的方案,代码如下:
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();
flags = NGX_UPDATE_TIME;
#if (NGX_THREADS)
if (timer == NGX_TIMER_INFINITE || timer > 500) {
timer = 500;
}
#endif
}
如果是ngx_timer_resolution,timer的值是无限长,如果结合epoll模型来看,就是epoll_wait的等待时间是无限长。如果很长时间没有任何I/O就绪,那么epoll会一直挂在这里,并且导致整个进程也挂起吗,显然不是,前面初始化的时候已经调用setitimer设置了一个定时器,因此当没有继续I/O而定时器周期耗尽的时候,SIGALARM信号就触发了,此信号首先会导致epoll_wait返回,并返回错误代码,另外会调用ngx_timer_signal_handler函数,设置ngx_event_timer_alarm为1,如果errno的值是,EINTR,那么epoll_wait是被因SIGALARM导致的退出,那么此时返回NGX_OK,否则返回NGX_ERROR,代码如下
if (flags & NGX_UPDATE_TIME) {
ngx_time_update(0, 0);
}
if (err) {
if (err == NGX_EINTR) {
if (ngx_event_timer_alarm) {
ngx_event_timer_alarm = 0;
return NGX_OK;
}
level = NGX_LOG_INFO;
} else {
level = NGX_LOG_ALERT;
}
ngx_log_error(level, cycle->log, err, "epoll_wait() failed");
return NGX_ERROR;
}