1. 为什么进行超时处理
2. 如何进行超时处理
3. nginx中超时处理的实现
超时事件对象的数据结构组织
我们已经知道nginx把事件封装在一个名为ngx_event_s的结构体内,而该结构体有几个字段与本节讲的nginx超时处理相关。
unsigned timedout:1;
unsigned timer_set:1;
ngx_rbtree_node_t timer;
timedout域字段,用于标识该当前事件是否已经超时,0为没有超时。
timer_set域字段,用于标识该当前事件是否已经加入到红黑树,需要对其是否超时做监控,0为没有加入。
tmer字段,很容易看出属于红黑树节点类型变量,红黑树就是通过该字段来组织事件对象。
nginx设置了两个全局变量以便在程序的任何地方都能快速的访问到事件计时红黑树:
ngx_thread_volatile ngx_rbtree_t ngx_event_timer_rbtree;
static ngx_rbtree_node_t ngx_event_timer_sentinel;
它们都定义在源文件ngx_event_timer.c内,结构体ngx_rbtree_s类型的全局变量ngx_event_timer_rbtree封装了事件计时红黑树树结构,而ngx_event_timer_sentinel属于红黑树节点类型变量,在红黑树的操作过程中当作哨兵使用,同时它是static的,所以作用域仅限于模块内。
定时器初始化(红黑树的初始化)
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
return NGX_ERROR;
}
调用ngx_event_timer_init函数完成定时器红黑树的建树操作,这棵红黑树在存储定时器的同时,也为epoll_wait提供了等待时间。
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");
}
}
使用setitimer系统调用设置系统定时器,每当到达时间点后将发生SIGALRM信号,同时epoll_wait的阻塞将被信号中断从而被唤醒执行定时事件。其实,这段初始化并不是一定会被执行的,它的条件ngx_timer_resolution就是通过配置指令timer_resolution来设置的,如果没有配置此指令,就不会执行这段初始化代码了。也就是说,配置文件中使用了timer_resolution指令后,epoll_wait将使用信号中断的机制来驱动定时器,否则将使用定时器红黑树的最小时间作为epoll_wait超时时间来驱动定时器。
epoll_wait的定时唤醒
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
ngx_uint_t flags;
ngx_msec_t timer, delta;
if (ngx_timer_resolution) {
timer = NGX_TIMER_INFINITE;
flags = 0;
} else {
timer = ngx_event_find_timer();//将超时检测时间设置为最快发生超时的事件对象的超时时刻与当前时刻之差
flags = NGX_UPDATE_TIME;
...
(void) ngx_process_events(cycle, timer, flags);
...
}
超时对象的两种检测方法
static ngx_int_t
ngx_event_process_init(ngx_cycle_t *cycle)
{
...
sa.sa_handler = ngx_timer_signal_handler;
sigemptyset(&sa.sa_mask);
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_timer_signal_handler:
static void
ngx_timer_signal_handler(int signo)
{
ngx_event_timer_alarm = 1;
#if 1
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer signal");
#endif
}
static ngx_int_t
ngx_epoll_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags)
{
...
events = epoll_wait(ep, event_list, (int) nevents, timer);
err = (events == -1) ? ngx_errno : 0;
if (flags & NGX_UPDATE_TIME || ngx_event_timer_alarm) {
ngx_time_update();
}
...
}
在方案一的情况下,||前面的式子为假,那么ngx_event_timer_alarm 不为1 的情况下,更新函数ngx_time_update()不会被执行。那么会导致超时检测函数ngx_event_expire_timers不会被执行。看ngx_process_events_and_timers函数的代码(ngx_event.c):
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
...
delta = ngx_current_msec;
(void) ngx_process_events(cycle, timer, flags);//事件处理函数
delta = ngx_current_msec - delta;
...
if (delta) {
ngx_event_expire_timers();//超时检测函数
}
...
}
当ngx_timer_resolution为0时,执行方案2。timer设置为最快发生超时的事件对象的超时时刻与当前时刻的时间差。具体计算时在函数ngx_event_find_timer内(ngx_event_timer.c)。
ngx_msec_t
ngx_event_find_timer(void)
{
ngx_msec_int_t timer;
ngx_rbtree_node_t *node, *root, *sentinel;
if (ngx_event_timer_rbtree.root == &ngx_event_timer_sentinel) {
return NGX_TIMER_INFINITE;
}
ngx_mutex_lock(ngx_event_timer_mutex);
root = ngx_event_timer_rbtree.root;
sentinel = ngx_event_timer_rbtree.sentinel;
node = ngx_rbtree_min(root, sentinel);
ngx_mutex_unlock(ngx_event_timer_mutex);
timer = (ngx_msec_int_t) (node->key - ngx_current_msec);
return (ngx_msec_t) (timer > 0 ? timer : 0);
}
该函数从红黑树中找到key值最小的节点,然后用key值减去当前时刻即得到预期timer值。这个值可能是负数,表示已经有事件超时了。因此直接将其设置为0.那么事件处理机制在开始监控I/O事件时会立即返回,以便马上处理这些超时事件。同时flags被设置为NGX_UPDATE_TIME。从ngx_epoll_process_events函数的代码中可以看出ngx_time_update()将被执行,事件被更新。即事件处理机制每次返回都会更新时间。如果I/O事件比较多,那么会导致比较频繁地调用gettimeofday()系统函数,这也可以说是超时检测方案2对性能的最大影响。这个时候超时检测函数ngx_event_expire_timers()函数会被执行。

4. 定时事件的使用
static ngx_connection_t dummy;
static ngx_event_t ev;
static void
ngx_http_hello_print(ngx_event_t *ev)
{
printf("hello world\n");
ngx_add_timer(ev, 1000);
}
static ngx_int_t
ngx_http_hello_process_init(ngx_cycle_t *cycle)
{
dummy.fd = (ngx_socket_t) -1;
ngx_memzero(&ev, sizeof(ngx_event_t));
ev.handler = ngx_http_hello_print;
ev.log = cycle->log;
ev.data = &dummy;
ngx_add_timer(&ev, 1000);
return NGX_OK;
}
这段代码将注册一个定时事件——每过一秒钟打印一次hello world。ngx_add_timer函数就是用来完成将一个新的定时事件加入定时器红黑树中,定时事件被执行后,就会从树中移除,因此要想不断的循环打印hello world,就需要在事件回调函数被调用后再将事件给添加到定时器红黑树中。 ngx_http_hello_process_init是注册在模块的进程初始化阶段的回调函数上。由于,ngx_even_core_module模块排在自定义模块的前面,所以我们在进程初始化阶段添加定时事件时,定时器已经被初始化好了。