关于超时event, 一开始接触libevent时遇到的example就是它, 小巧而简单易懂, 但是其内部却因为种种因素而十分庞杂.
一个单纯的定时器event如下定义:
在看如何添加定定时器之前, 要声明的是libevent在最初是只使用小顶堆对定时器进行管理的, 后来发现可能会在同一时间处理许多具有相同超时时间的事件, 这样一来, 使用小顶堆就没有优势了, 于是有使用common timeout进行辅助处理,以提升性能(先不谈, 文章最后部分讨论)
一个单纯的定时器event如下定义:
#define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
其实, 如果需要一个event拥有定时器的功能, 都只需要在event_add时将第二个参数不置为NULL就行
如何添加定时器
在看如何添加定定时器之前, 要声明的是libevent在最初是只使用小顶堆对定时器进行管理的, 后来发现可能会在同一时间处理许多具有相同超时时间的事件, 这样一来, 使用小顶堆就没有优势了, 于是有使用common timeout进行辅助处理,以提升性能(先不谈, 文章最后部分讨论)
//在了解event_add函数内部源码之前,我们先看看官方对于event_add的介绍
//if you call event_add on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout
//if the event, is already pending ,and you re-add it with the timeout NULL, event_add will have no effect
static inline int
event_add_internal(struct event *ev, const struct timeval *tv,
int tv_is_absolute)
{
... ... //一些准备
/*
* prepare for timeout insertion further below, if we get a
* failure on any step, we should not change any state.
*/
//接下来我们就要开始正式插入过程了
//只是当我们调用此函数时,我们可能做几件事情:将事件注册到event_base中; 若事件有定时器则把事件放到时间堆中/链表中
//这两件事要么一起做,要么都不做,需要有原子的性质,所以这里采取了行动
//这里我们发现此事件拥有定时器(tv!=NULL), 且它尚未被添加到超时事件链表中去
//于是我们调用min_heap_reserve函数,目的只是增加堆的大小,提前为我们插入定时器作准备
//还要注意的是,我们只是做准备,没有真的插入,因为要满足原子性,此时插入定时器肯定是会成功的,但如果下面的事件插入失败了,那就打破原子性了
//如果下面事件插入失败了,那也就退出了,也不会插入定时器,所以能形成原子性,只是会导致可能的堆容量增大,影响不大
if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
if (min_heap_reserve(&base->timeheap,
1 + min_heap_size(&base->timeheap)) == -1)
return (-1); /* ENOMEM == errno */
}
... ... //假设事件的插入成功了
/*
* we should change the timeout state only if the previous event
* addition succeeded.
*/
//现在,判断上面的事件插入操作是否成功,成功的话我们处理定时器了
//再来回顾一下此函数的说明:
//if you call event_add on an event that is already pending, it will leave it pending, and reschedule it with the provided timeout
//if the event, is already pending ,and you re-add it with the timeout NULL, event_add will have no effect
if (res != -1 && tv != NULL) {
struct timeval now;
int common_timeout;
/*
* for persistent timeout events, we remember the
* timeout value and re-add the event.
*
* If tv_is_absolute, this was already set.
*/
//对于永久(带EV_PERSIST标志)的事件当然带有永久的定时器,我们记住它的超时时间,每次超时结束后继续通过该时间设置定时器
//如果是绝对时间的,似乎也没必要设置PERSIST标志了
//所以现在我们通过ev_io_timeout记住这个超时时间供下次使用,这个ev_io_timeout写具体了就是:
// ev->_ev.ev_io.ev_timeout
//如果events中包含EV_PERSIST属性,那么下面的closure标识是在调用event_assign时被设置的
if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
ev->ev_io_timeout = *tv;
/*
* we already reserved memory above for the case where we
* are not replacing an existing timeout.
*/
//如果这个事件之前就有定时器,现在又对它调用event_add,那么就要覆盖之前的定时器
//之所以会出现这样的情况, 大致是因为事件在被触发时就被删除了, 但是触发该事件的不是超时原因, 这里再次添加该事件, 那么超时时间就被重置了
//举个例子, 接收用户的数据, 保持长连接. 如果一段时间没有收到就断开连接. 但是在超时时间内收到了用户数据, 那么此时就要重置定时器
if (ev->ev_flags & EVLIST_TIMEOUT) {
/* XXX I believe this is needless. */
//判断此事件是否为堆顶元素,如果是的话,就需要提醒主线程
if (min_heap_elt_is_top(ev))
notify = 1;
//将此定时器从定时器队列中删除,因为新的超时时间会覆盖旧的超时
//虽然一个event可以被多次evnet_add, 但是对于它的定时器来说却只能有一个
event_queue_remove(base, ev, EVLIST_TIMEOUT);
}
/* Check if it is active due to a timeout. Rescheduling
* this timeout before the callback can be executed
* removes it from the active list. */
//如果此事件已经被激活了, 且激活的原因正是超时, 那么我们在执行其回调函数前将其移除激活队列
if ((ev->ev_flags & EVLIST_ACTIVE) &&
(ev->ev_res & EV_TIMEOUT)) {
if (ev->ev_events & EV_SIGNAL) {
/* See if we are just active executing
* this event in a loop
*/
if (ev->ev_ncalls && ev->ev_pncalls) {
/* Abort loop */
*ev->ev_pncalls = 0;
}
}
event_queue_remove(base, ev, EVLIST_ACTIVE);
}
//得到当前时间
gettime(base, &now);
//暂且不谈common_timeout, 下面会详细分析. 这里一段代码目的是得到此事件的绝对超时时间
common_timeout = is_common_timeout(tv, base);
if (tv_is_absolute) {
//用ev_timeout记录下此事件要在绝对时间tv发生
ev->ev_timeout = *tv;
} else if (common_timeout) {
struct timeval tmp = *tv;
tmp.tv_usec &= MICROSECONDS_MASK;
evutil_timeradd(&now, &tmp, &ev->ev_timeout);
ev->ev_timeout.tv_usec |=
(tv->tv_usec & ~MICROSECONDS_MASK);
} else {
evutil_timeradd(&now, tv, &ev->ev_timeout);
}
//将拥有定时器的事件也放到超时队列中去, 这样flags中就有了EVLIST_TIMEOUT
//与此同时, 将定时器插入到小顶堆或者common timeout中去
event_queue_insert(base, ev, EVLIST_TIMEOUT);
if (common_timeout) {
struct common_timeout_list *ctl =
get_common_timeout_list(base, &ev->ev_timeout);
if (ev == TAILQ_FIRST(&ctl->events)) {
common_timeout_schedule(ctl, &now, ev);
}
} else {