如何成为超时event:
Libevent允许创建一个超时event,使用evtimer_new宏。
-
- #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
从宏的实现来看,它一样是用到了一般的event_new,并且不使用任何的文件描述符。从超时event宏的实现来看,无论是evtimer创建的event还是一般event_new创建的event,都能使得Libevent进行超时监听。其实,使得Libevent对一个event进行超时监听的原因是:在调用event_add的时候,第二参数不能为NULL,要设置一个超时值。如果为NULL,那么Libevent将不会为这个event监听超时。下文统一称设置了超时值的event为超时event。
超时event的原理:
Libevent对超时进行监听的原理不同于之前讲到的对信号的监听,Libevent对超时的监听的原理是,多路IO复用函数都是有一个超时值。如果用户需要Libevent同时监听多个超时event,那么Libevent就把超时值最小的那个作为多路IO复用函数的超时值。自然,当时间一到,就会从多路IO复用函数返回。此时对超时event进行处理即可。
Libevent运行用户同时监听多个超时event,那么就必须要对这个超时值进行管理。Libevent提供了小根堆和通用超时(common timeout)这两种管理方式。下文为了叙述方便,就假定使用的是小根堆。
工作流程:
下面来看一下超时event的工作流程。
设置超时值:
首先调用event_add时要设置一个超时值,这样才能成为一个超时event。
-
-
- static inline int
- event_add_internal(struct event *ev, const struct timeval *tv,
- int tv_is_absolute)
- {
- struct event_base *base = ev->ev_base;
- int res = 0;
- int notify = 0;
-
-
- if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
- if (min_heap_reserve(&base->timeheap,
- 1 + min_heap_size(&base->timeheap)) == -1)
- return (-1);
- }
-
- ...
-
-
- if (res != -1 && tv != NULL) {
- struct timeval now;
-
-
-
-
- if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
- ev->ev_io_timeout = *tv;
-
-
-
- if (ev->ev_flags & EVLIST_TIMEOUT) {
-
-
-
-
- if (min_heap_elt_is_top(ev))
- notify = 1;
-
-
-
- event_queue_remove(base, ev, EVLIST_TIMEOUT);
- }
-
-
- if ((ev->ev_flags & EVLIST_ACTIVE) &&
- (ev->ev_res & EV_TIMEOUT)) {
-
- ...
- event_queue_remove(base, ev, EVLIST_ACTIVE);
- }
-
-
- gettime(base, &now);
-
-
-
-
- if (tv_is_absolute) {
- ev->ev_timeout = *tv;
- } else {
-
- evutil_timeradd(&now, tv, &ev->ev_timeout);
- }
-
-
-
- event_queue_insert(base, ev, EVLIST_TIMEOUT);
-
-
- if (min_heap_elt_is_top(ev))
- notify = 1;
- }
-
-
- if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
- evthread_notify_base(base);
-
- return (res);
- }
对于同一个event,如果是IO event或者信号event,那么将无法多次添加。但如果是一个超时event,那么是可以多次添加的。并且对应超时值会使用最后添加时指明的那个,之前的统统不要,即替换掉之前的超时值。
代码中出现了多次使用了notify变量。这主要是用在:次线程在执行这个函数,而主线程在执行event_base_dispatch。前面说到Libevent能对超时event进行监听的原理是:多路IO复用函数有一个超时参数。在次线程添加的event的超时值更小,又或者替换了之前最小的超时值。在这种情况下,都是要通知主线程,告诉主线程,最小超时值已经变了。关于通知主线程evthread_notify_base,可以参考博文《evthread_notify_base通知主线程》。
代码中的第三个判断体中用到了ev->ev_io_timeout。但event结构体中并没有该变量。其实,ev_io_timeout是一个宏定义。
-
- #define ev_io_timeout _ev.ev_io.ev_timeout
要注意的一点是,在调用event_add时设定的超时值是一个时间段(可以认为隔多长时间就触发一次),相对于现在,即调用event_add的时间,而不是调用event_base_dispatch的时间。
调用多路IO复用函数等待超时:
现在来看一下event_base_loop函数,看其是怎么处理超时event的。
-
- int
- event_base_loop(struct event_base *base, int flags)
- {
- const struct eventop *evsel = base->evsel;
- struct timeval tv;
- struct timeval *tv_p;
- int res, done, retval = 0;
-
- EVBASE_ACQUIRE_LOCK(base, th_base_lock);
-
- base->running_loop = 1;
-
- done = 0;
- while (!done) {
- tv_p = &tv;
- if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
-
- timeout_next(base, &tv_p);
- } else {
-
- evutil_timerclear(&tv);
- }
-
- res = evsel->dispatch(base, tv_p);
-
-
- timeout_process(base);
-
- if (N_ACTIVE_CALLBACKS(base)) {
- int n = event_process_active(base);
- }
- }
-
- done:
- base->running_loop = 0;
- EVBASE_RELEASE_LOCK(base, th_base_lock);
-
- return (retval);
- }
-
-
-
- static int
- timeout_next(struct event_base *base, struct timeval **tv_p)
- {
-
- struct timeval now;
- struct event *ev;
- struct timeval *tv = *tv_p;
- int res = 0;
-
-
- ev = min_heap_top(&base->timeheap);
-
-
- if (ev == NULL) {
- *tv_p = NULL;
- goto out;
- }
-
-
- if (gettime(base, &now) == -1) {
- res = -1;
- goto out;
- }
-
-
-
-
-
- if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
- evutil_timerclear(tv);
- goto out;
- }
-
-
- evutil_timersub(&ev->ev_timeout, &now, tv);
-
- out:
- return (res);
- }
上面代码的流程是:计算出本次调用多路IO复用函数的等待时间,然后调用多路IO复用函数中等待超时。
激活超了时的event:
上面代码中的timeout_process函数就是处理超了时的event。
-
-
- static void
- timeout_process(struct event_base *base)
- {
-
- struct timeval now;
- struct event *ev;
-
- if (min_heap_empty(&base->timeheap)) {
- return;
- }
-
- gettime(base, &now);
-
-
-
- while ((ev = min_heap_top(&base->timeheap))) {
-
-
- if (evutil_timercmp(&ev->ev_timeout, &now, >))
- break;
-
-
-
-
-
-
-
- event_del_internal(ev);
-
-
-
-
- event_active_nolock(ev, EV_TIMEOUT, 1);
- }
- }
当从多路IO复用函数返回时,就检查时间小根堆,看有多少个event已经超时了。如果超时了,那就把这个event加入到event_base的激活队列中。并且把这个超时del(删除)掉,这主要是用于非PERSIST 超时event的。删除一个event的具体操作可以查看这里。
把一个event添加进激活队列后的工作流程可以参考《Libevent工作流程探究》一文。
处理永久超时event:
现在来看一下如果该超时event有EV_PERSIST选项,在后面是怎么再次添加进event_base,因为前面的代码注释中已经说了,在选出超时event时,会把超时的event从event_base中delete掉。
-
- int
- event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd,
- short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
- {
- ...
- if (events & EV_PERSIST) {
- ev->ev_closure = EV_CLOSURE_PERSIST;
- } else {
- ev->ev_closure = EV_CLOSURE_NONE;
- }
-
- return 0;
- }
-
-
- static int
- event_process_active_single_queue(struct event_base *base,
- struct event_list *activeq)
- {
- struct event *ev;
-
-
- for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
-
-
-
-
- if (ev->ev_events & EV_PERSIST)
- event_queue_remove(base, ev, EVLIST_ACTIVE);
- else
- event_del_internal(ev);
-
-
- switch (ev->ev_closure) {
-
- case EV_CLOSURE_PERSIST:
- event_persist_closure(base, ev);
- break;
-
- default:
- case EV_CLOSURE_NONE:
-
- (*ev->ev_callback)(
- ev->ev_fd, ev->ev_res, ev->ev_arg);
- break;
- }
- }
- }
-
-
- static inline void
- event_persist_closure(struct event_base *base, struct event *ev)
- {
-
-
-
- if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec) {
- struct timeval run_at, relative_to, delay, now;
- ev_uint32_t usec_mask = 0;
-
- gettime(base, &now);
-
-
- delay = ev->ev_io_timeout;
-
-
- if (ev->ev_res & EV_TIMEOUT) {
- relative_to = ev->ev_timeout;
- } else {
- relative_to = now;
- }
-
- evutil_timeradd(&relative_to, &delay, &run_at);
-
-
-
- if (evutil_timercmp(&run_at, &now, <)) {
-
- evutil_timeradd(&now, &delay, &run_at);
- }
-
-
- event_add_internal(ev, &run_at, 1);
- }
- EVBASE_RELEASE_LOCK(base, th_base_lock);
- (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
- }
这段代码的处理流程是:如果用户指定了EV_PERSIST,那么在event_assign中就记录下来。在event_process_active_single_queue函数中会针对永久event进行调用event_persist_closure函数对之进行处理。在event_persist_closure函数中,如果是一般的永久event,那么就直接调用该event的回调函数。如果是超时永久event,那么就需要再次计算新的超时时间,并将这个event再次插入到event_base中。
这段代码也指明了,如果一个event因可读而被激活,那么其超时时间就要重新计算。而不是之前的那个了。也就是说,如果一个event设置了3秒的超时,但1秒后就可读了,那么下一个超时值,就要重新计算设置,而不是2秒后。
从前面的源码分析也可以得到:如果一个event监听可读的同时也设置了超时值,并且一直没有数据可读,最后超时了,那么这个event将会被删除掉,不会再等。
如何成为超时event:
Libevent允许创建一个超时event,使用evtimer_new宏。
-
- #define evtimer_new(b, cb, arg) event_new((b), -1, 0, (cb), (arg))
从宏的实现来看,它一样是用到了一般的event_new,并且不使用任何的文件描述符。从超时event宏的实现来看,无论是evtimer创建的event还是一般event_new创建的event,都能使得Libevent进行超时监听。其实,使得Libevent对一个event进行超时监听的原因是:在调用event_add的时候,第二参数不能为NULL,要设置一个超时值。如果为NULL,那么Libevent将不会为这个event监听超时。下文统一称设置了超时值的event为超时event。
超时event的原理:
Libevent对超时进行监听的原理不同于之前讲到的对信号的监听,Libevent对超时的监听的原理是,多路IO复用函数都是有一个超时值。如果用户需要Libevent同时监听多个超时event,那么Libevent就把超时值最小的那个作为多路IO复用函数的超时值。自然,当时间一到,就会从多路IO复用函数返回。此时对超时event进行处理即可。
Libevent运行用户同时监听多个超时event,那么就必须要对这个超时值进行管理。Libevent提供了小根堆和通用超时(common timeout)这两种管理方式。下文为了叙述方便,就假定使用的是小根堆。
工作流程:
下面来看一下超时event的工作流程。
设置超时值:
首先调用event_add时要设置一个超时值,这样才能成为一个超时event。
-
-
- static inline int
- event_add_internal(struct event *ev, const struct timeval *tv,
- int tv_is_absolute)
- {
- struct event_base *base = ev->ev_base;
- int res = 0;
- int notify = 0;
-
-
- if (tv != NULL && !(ev->ev_flags & EVLIST_TIMEOUT)) {
- if (min_heap_reserve(&base->timeheap,
- 1 + min_heap_size(&base->timeheap)) == -1)
- return (-1);
- }
-
- ...
-
-
- if (res != -1 && tv != NULL) {
- struct timeval now;
-
-
-
-
- if (ev->ev_closure == EV_CLOSURE_PERSIST && !tv_is_absolute)
- ev->ev_io_timeout = *tv;
-
-
-
- if (ev->ev_flags & EVLIST_TIMEOUT) {
-
-
-
-
- if (min_heap_elt_is_top(ev))
- notify = 1;
-
-
-
- event_queue_remove(base, ev, EVLIST_TIMEOUT);
- }
-
-
- if ((ev->ev_flags & EVLIST_ACTIVE) &&
- (ev->ev_res & EV_TIMEOUT)) {
-
- ...
- event_queue_remove(base, ev, EVLIST_ACTIVE);
- }
-
-
- gettime(base, &now);
-
-
-
-
- if (tv_is_absolute) {
- ev->ev_timeout = *tv;
- } else {
-
- evutil_timeradd(&now, tv, &ev->ev_timeout);
- }
-
-
-
- event_queue_insert(base, ev, EVLIST_TIMEOUT);
-
-
- if (min_heap_elt_is_top(ev))
- notify = 1;
- }
-
-
- if (res != -1 && notify && EVBASE_NEED_NOTIFY(base))
- evthread_notify_base(base);
-
- return (res);
- }
对于同一个event,如果是IO event或者信号event,那么将无法多次添加。但如果是一个超时event,那么是可以多次添加的。并且对应超时值会使用最后添加时指明的那个,之前的统统不要,即替换掉之前的超时值。
代码中出现了多次使用了notify变量。这主要是用在:次线程在执行这个函数,而主线程在执行event_base_dispatch。前面说到Libevent能对超时event进行监听的原理是:多路IO复用函数有一个超时参数。在次线程添加的event的超时值更小,又或者替换了之前最小的超时值。在这种情况下,都是要通知主线程,告诉主线程,最小超时值已经变了。关于通知主线程evthread_notify_base,可以参考博文《evthread_notify_base通知主线程》。
代码中的第三个判断体中用到了ev->ev_io_timeout。但event结构体中并没有该变量。其实,ev_io_timeout是一个宏定义。
-
- #define ev_io_timeout _ev.ev_io.ev_timeout
要注意的一点是,在调用event_add时设定的超时值是一个时间段(可以认为隔多长时间就触发一次),相对于现在,即调用event_add的时间,而不是调用event_base_dispatch的时间。
调用多路IO复用函数等待超时:
现在来看一下event_base_loop函数,看其是怎么处理超时event的。
-
- int
- event_base_loop(struct event_base *base, int flags)
- {
- const struct eventop *evsel = base->evsel;
- struct timeval tv;
- struct timeval *tv_p;
- int res, done, retval = 0;
-
- EVBASE_ACQUIRE_LOCK(base, th_base_lock);
-
- base->running_loop = 1;
-
- done = 0;
- while (!done) {
- tv_p = &tv;
- if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
-
- timeout_next(base, &tv_p);
- } else {
-
- evutil_timerclear(&tv);
- }
-
- res = evsel->dispatch(base, tv_p);
-
-
- timeout_process(base);
-
- if (N_ACTIVE_CALLBACKS(base)) {
- int n = event_process_active(base);
- }
- }
-
- done:
- base->running_loop = 0;
- EVBASE_RELEASE_LOCK(base, th_base_lock);
-
- return (retval);
- }
-
-
-
- static int
- timeout_next(struct event_base *base, struct timeval **tv_p)
- {
-
- struct timeval now;
- struct event *ev;
- struct timeval *tv = *tv_p;
- int res = 0;
-
-
- ev = min_heap_top(&base->timeheap);
-
-
- if (ev == NULL) {
- *tv_p = NULL;
- goto out;
- }
-
-
- if (gettime(base, &now) == -1) {
- res = -1;
- goto out;
- }
-
-
-
-
-
- if (evutil_timercmp(&ev->ev_timeout, &now, <=)) {
- evutil_timerclear(tv);
- goto out;
- }
-
-
- evutil_timersub(&ev->ev_timeout, &now, tv);
-
- out:
- return (res);
- }
上面代码的流程是:计算出本次调用多路IO复用函数的等待时间,然后调用多路IO复用函数中等待超时。
激活超了时的event:
上面代码中的timeout_process函数就是处理超了时的event。
-
-
- static void
- timeout_process(struct event_base *base)
- {
-
- struct timeval now;
- struct event *ev;
-
- if (min_heap_empty(&base->timeheap)) {
- return;
- }
-
- gettime(base, &now);
-
-
-
- while ((ev = min_heap_top(&base->timeheap))) {
-
-
- if (evutil_timercmp(&ev->ev_timeout, &now, >))
- break;
-
-
-
-
-
-
-
- event_del_internal(ev);
-
-
-
-
- event_active_nolock(ev, EV_TIMEOUT, 1);
- }
- }
当从多路IO复用函数返回时,就检查时间小根堆,看有多少个event已经超时了。如果超时了,那就把这个event加入到event_base的激活队列中。并且把这个超时del(删除)掉,这主要是用于非PERSIST 超时event的。删除一个event的具体操作可以查看这里。
把一个event添加进激活队列后的工作流程可以参考《Libevent工作流程探究》一文。
处理永久超时event:
现在来看一下如果该超时event有EV_PERSIST选项,在后面是怎么再次添加进event_base,因为前面的代码注释中已经说了,在选出超时event时,会把超时的event从event_base中delete掉。
-
- int
- event_assign(struct event *ev, struct event_base *base, evutil_socket_t fd,
- short events, void (*callback)(evutil_socket_t, short, void *), void *arg)
- {
- ...
- if (events & EV_PERSIST) {
- ev->ev_closure = EV_CLOSURE_PERSIST;
- } else {
- ev->ev_closure = EV_CLOSURE_NONE;
- }
-
- return 0;
- }
-
-
- static int
- event_process_active_single_queue(struct event_base *base,
- struct event_list *activeq)
- {
- struct event *ev;
-
-
- for (ev = TAILQ_FIRST(activeq); ev; ev = TAILQ_FIRST(activeq)) {
-
-
-
-
- if (ev->ev_events & EV_PERSIST)
- event_queue_remove(base, ev, EVLIST_ACTIVE);
- else
- event_del_internal(ev);
-
-
- switch (ev->ev_closure) {
-
- case EV_CLOSURE_PERSIST:
- event_persist_closure(base, ev);
- break;
-
- default:
- case EV_CLOSURE_NONE:
-
- (*ev->ev_callback)(
- ev->ev_fd, ev->ev_res, ev->ev_arg);
- break;
- }
- }
- }
-
-
- static inline void
- event_persist_closure(struct event_base *base, struct event *ev)
- {
-
-
-
- if (ev->ev_io_timeout.tv_sec || ev->ev_io_timeout.tv_usec) {
- struct timeval run_at, relative_to, delay, now;
- ev_uint32_t usec_mask = 0;
-
- gettime(base, &now);
-
-
- delay = ev->ev_io_timeout;
-
-
- if (ev->ev_res & EV_TIMEOUT) {
- relative_to = ev->ev_timeout;
- } else {
- relative_to = now;
- }
-
- evutil_timeradd(&relative_to, &delay, &run_at);
-
-
-
- if (evutil_timercmp(&run_at, &now, <)) {
-
- evutil_timeradd(&now, &delay, &run_at);
- }
-
-
- event_add_internal(ev, &run_at, 1);
- }
- EVBASE_RELEASE_LOCK(base, th_base_lock);
- (*ev->ev_callback)(ev->ev_fd, ev->ev_res, ev->ev_arg);
- }
这段代码的处理流程是:如果用户指定了EV_PERSIST,那么在event_assign中就记录下来。在event_process_active_single_queue函数中会针对永久event进行调用event_persist_closure函数对之进行处理。在event_persist_closure函数中,如果是一般的永久event,那么就直接调用该event的回调函数。如果是超时永久event,那么就需要再次计算新的超时时间,并将这个event再次插入到event_base中。
这段代码也指明了,如果一个event因可读而被激活,那么其超时时间就要重新计算。而不是之前的那个了。也就是说,如果一个event设置了3秒的超时,但1秒后就可读了,那么下一个超时值,就要重新计算设置,而不是2秒后。
从前面的源码分析也可以得到:如果一个event监听可读的同时也设置了超时值,并且一直没有数据可读,最后超时了,那么这个event将会被删除掉,不会再等。