基本时间操作函数:
Libevent采用的时间类型是struct timeval,这个类型在很多平台都提供了。此外,Libevent还提供了一系列的时间操作函数。比如两个struct timeval相加、相减、比较大小。有些平台直接提供了一些时间操作函数,但有些则没有,那么Libevent就自己实现。这些宏如下:
- #ifdef _EVENT_HAVE_TIMERADD
- #define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))
- #define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))
- #else
- #define evutil_timeradd(tvp, uvp, vvp) \
- do { \
- (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
- (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
- if ((vvp)->tv_usec >= 1000000) { \
- (vvp)->tv_sec++; \
- (vvp)->tv_usec -= 1000000; \
- } \
- } while (0)
- #define evutil_timersub(tvp, uvp, vvp) \
- do { \
- (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
- (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
- if ((vvp)->tv_usec < 0) { \
- (vvp)->tv_sec--; \
- (vvp)->tv_usec += 1000000; \
- } \
- } while (0)
- #endif
-
- #ifdef _EVENT_HAVE_TIMERCLEAR
- #define evutil_timerclear(tvp) timerclear(tvp)
- #else
- #define evutil_timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0
- #endif
-
-
- #define evutil_timercmp(tvp, uvp, cmp) \
- (((tvp)->tv_sec == (uvp)->tv_sec) ? \
- ((tvp)->tv_usec cmp (uvp)->tv_usec) : \
- ((tvp)->tv_sec cmp (uvp)->tv_sec))
-
- #ifdef _EVENT_HAVE_TIMERISSET
- #define evutil_timerisset(tvp) timerisset(tvp)
- #else
- #define evutil_timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
- #endif
代码中的那些条件宏,是在配置Libevent的时候检查所在的系统环境而定义的。具体的内容,可以参考《 event-config.h指明所在系统的环境
》一文。
Libevent的时间一般是用在超时event的。对于超时event,用户只需给出一个超时时间,比如多少秒,而不是一个绝对时间。但在Libevent内部,要将这个时间转换成绝对时间。所以在Libevent内部会经常获取系统时间(绝对时间),然后进行一些处理,比如,转换、比较。
cache时间:
Libevent封装了一个evutil_gettimeofday函数用来获取系统时间,该函数在POSIX的系统是直接调用gettimeofday函数,在Windows系统是通过_ftime函数。虽然gettimeofday的 耗时成本不大
,不过Libevent还是使用了一个cache保存时间,使得更加高效。在event_base结构体有一个struct timeval类型的cache变量 tv_cache。处理超时event的两个函数event_add_internal和event_base_loop内部都是调用gettime函数获取时间的。gettime函数如下:
-
- static int
- gettime(struct event_base *base, struct timeval *tp)
- {
- if (base->tv_cache.tv_sec) {
- *tp = base->tv_cache;
- return (0);
- }
-
- …
- }
从上面代码可以看到,Libevent优先使用cache时间。tv_bache变量处理作为cache外,还有另外一个作用,下面会讲到。
cache的时间也是通过调用系统的提供的时间函数得到的。
-
- static inline void
- update_time_cache(struct event_base *base)
- {
- base->tv_cache.tv_sec = 0;
- if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))
- gettime(base, &base->tv_cache);
- }
tv_cache是通过调用gettime来获取时间。由于tv_cache.tv_sec已经赋值为0,所以它将使用系统提供的时间函数得到时间。代码也展示了,如果event_base的配置中定义了EVENT_BASE_FLAG_NO_CACHE_TIME宏,将不能使用cache时间。关于这个宏的设置可以参考《配置event_base》一文。
处理用户手动修改系统时间:
如果用户能老老实实,或许代码就不需要写得很复杂。由于用户的不老实,所以有时候要考虑很多很特殊的情况。在Libevent的时间管理这方面也是如此。
Libevent在实际使用时还有一个坑爹的现象,那就是,用户手动把时钟(wall time)往回调了。比如说现在是上午9点,但用户却把OS的系统时间调成了上午7点。这是很坑爹的。对于超时event和event_add的第二个参数,都是一个时间长度。但在内部Libevent要把这个时间转换成绝对时间。
如果用户手动修改了OS的系统时间。那么Libevent把超时时间长度转换成绝对时间将是弄巧成拙。拿上面的时间例子。如果用户设置的超时为1分钟。那么到了9:01就会超时。如果用户把系统时间调成了7点,那么要过2个小时01分才能发生超时。这就和用户原先的设置差得很远了。
读者可能会说,这个责任应该是由用户负。呵呵,但Libevent提供的函数接口是一个时间长度,既然是时间长度,那么无论用户怎么改变OS的系统时间,这个时间长度都是相对于event_add ()被调用的那一刻算起,这是不会变的。如果Libevent做不到这一点,这说明是Libevent没有遵循接口要求。
为此,Libevent提出了一些解决方案。
使用monotonic时间:
问题的由来是因为用户能修改系统时间,所以最简单的解决方案就是能获取到一个用户不能修改的时间,然后以之为绝对时间。因为event_add提供给用户的接口使用的是一个时间长度,所以无论是使用哪个绝对时间都是无所谓的。
基于这一点,Libevent找到了monotonic时间,从字面来看monotonic翻译成单调。我们高中学过的单调函数英文名就是monotonic function。monotonic时间就像单调递增函数那样,只增不减的,没有人能手动修改之。
monotonic时间是boot启动后到现在的时间。用户是不能修改这个时间。如果Libevent所在的系统支持monotonic时间的话,那么Libevent就会选用这个monotonic时间为绝对时间。
首先,Libevent检查所在的系统是否支持monotonic时间。在event_base_new_with_config函数中会调用detect_monotonic函数检测。
-
- static void
- detect_monotonic(void)
- {
- #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
- struct timespec ts;
- static int use_monotonic_initialized = 0;
-
- if (use_monotonic_initialized)
- return;
-
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- use_monotonic = 1;
- use_monotonic_initialized = 1;
- #endif
- }
从上面代码可以看到,如果Libevent所在的系统支持monotonic时间,就将全局变量use_monotonic赋值1,作为标志。
如果Libevent所在的系统支持monotonic时间,那么Libevent将使用monotonic时间,也就是说Libevent用于获取系统时间的函数gettime将由monotonic提供时间。
-
- static int
- gettime(struct event_base *base, struct timeval *tp)
- {
- EVENT_BASE_ASSERT_LOCKED(base);
-
- if (base->tv_cache.tv_sec) {
- *tp = base->tv_cache;
- return (0);
- }
-
- #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
- if (use_monotonic) {
- struct timespec ts;
-
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
- return (-1);
-
- tp->tv_sec = ts.tv_sec;
- tp->tv_usec = ts.tv_nsec / 1000;
-
-
- if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL
- < ts.tv_sec) {
- struct timeval tv;
- evutil_gettimeofday(&tv,NULL);
-
- evutil_timersub(&tv, tp, &base->tv_clock_diff);
- base->last_updated_clock_diff = ts.tv_sec;
- }
-
- return (0);
- }
- #endif
-
-
- return (evutil_gettimeofday(tp, NULL));
- }
上面的代码虽然首先是使用cache时间,但实际上event_base结构体的cache时间也是通过调用gettime函数而得到的。上面代码也可以看到:如果所在的系统没有提供monotonic时间,那么就只能使用evutil_gettimeofday这个函数提供的系统时间了。
从上面的分析可知,如果Libevent所在的系统支持monotonic时间,那么根本就不用考虑用户手动修改系统时间这坑爹的事情。但如果所在的系统没有支持monotonic时间,那么Libevent就只能使用evutil_gettimeofday获取一个用户能修改的时间。
尽可能精确记录时间差:
现在来看一下Libevent在这种情况下在怎么解决这个坑爹得的问题。
Libevent给出的方案是,尽可能精确地计算 用户往回调了多长时间。如果知道了用户往回调了多长时间,那么将小根堆中的全部event的时间都往回调一样的时间即可。Libevent调用timeout_correct函数处理这个问题。
-
- static void
- timeout_correct(struct event_base *base, struct timeval *tv)
- {
-
- struct event **pev;
- unsigned int size;
- struct timeval off;
- int i;
-
-
- if (use_monotonic)
- return;
-
-
- gettime(base, tv);
-
-
-
- if (evutil_timercmp(tv, &base->event_tv, >=)) {
- base->event_tv = *tv;
- return;
- }
-
- evutil_timersub(&base->event_tv, tv, &off);
-
- pev = base->timeheap.p;
- size = base->timeheap.n;
-
-
- for (; size-- > 0; ++pev) {
- struct timeval *ev_tv = &(**pev).ev_timeout;
-
-
- evutil_timersub(ev_tv, &off, ev_tv);
- }
-
-
- base->event_tv = *tv;
- }
Libevent用event_base的成员变量event_tv保存用户修改系统时间前的系统时间。如果刚保存完,用户就修改系统时间,这样就能精确地计算出用户往回调了多长时间。但毕竟Libevent是用户态的库,不能做到用户修改系统时间前的一刻保存系统时间。
于是Libevent采用多采点的方式,即时不时就保存一次系统时间。所以在event_base_loop函数中的while循环体里面会有gettime(base, &base->event_tv);这是为了能多采点。但这个while循环里面还会执行多路IO复用函数和处理被激活event的回调函数(这个回调函数执行多久也是个未知数)。这两个函数的执行需要的时间可能会比较长,如果用户刚才是在执行完这两个函数之后修改系统时间,那么event_tv保存的时间就不怎么精确了。这也是没有办法的啊!!唉!!
下面贴出event_base_loop函数
-
- 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;
-
-
- clear_time_cache(base);
-
-
- while (!done) {
- timeout_correct(base, &tv);
-
- tv_p = &tv;
- if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
-
- timeout_next(base, &tv_p);
- } else {
- evutil_timerclear(&tv);
- }
-
-
- gettime(base, &base->event_tv);
-
-
-
-
-
-
- clear_time_cache(base);
-
-
- res = evsel->dispatch(base, tv_p);
-
-
- update_time_cache(base);
-
-
- timeout_process(base);
-
- if (N_ACTIVE_CALLBACKS(base)) {
- int n = event_process_active(base);
- }
- }
-
- return (retval);
- }
可以看到,在dispatch和event_process_active之间有一个update_time_cache。而前面的gettime(base,&base->event_tv);实际上取的就是cache的时间。所以,如果该Libevent支持cache的话,会精确那么一些。一般来说,用户为event设置的回调函数,不应该执行太久的时间。这也是tv_cache时间的另外一个作用。
出现的bug:
由于Libevent的解决方法并不是很精确,所以还是会有一些bug。下面给出一个bug。如果用户是在调用event_new函数之后,event_add之前对系统时间进行修改,那么无论用户设置的event超时有多长,都会马上触发超时。下面给出实际的例子
。这个例子要运行在不支持
monotonic
时间的系统
,我是在Windows运行的。
- #include <event2/event.h>
- #include<stdio.h>
-
-
- void timeout_cb(int fd, short event, void *arg)
- {
- printf("in the timeout_cb\n");
- }
-
-
- int main()
- {
- struct event_base *base = event_base_new();
-
- struct event *ev = event_new(base, -1, EV_TIMEOUT, timeout_cb, NULL);
-
- int ch;
-
- scanf("%c", &ch);
-
- struct timeval tv = {100, 0};
-
- event_add(ev, &tv);
-
- event_base_dispatch(base);
-
- return 0;
- }
这个bug的出现是因为,在event_base_new_with_config函数中有gettime(base,&base->event_tv),所以event_tv记录了修改前的时间。而event_add是在修改系统时间后才调用的。所以event结构体的ev_timeout变量使用的是修改系统时间后的超时时间,这是正确的时间。在执行timeout_correct函数时,Libevent发现用户修改了系统时间,所以就将本来正确的ev_timeout减去了off。所以ev_timeout就变得比较修改后的系统时间小了。在后面检查超时时,就会发现该event已经超时了(实际是没有超时),就把它触发。
如果该event有EV_PERSIST属性,那么之后的超时则会是正确的。这个留给读者去分析吧。
另外,Libevent并没有考虑把时钟往后调,比如现在是9点,用户把系统时间调成10点。上面的代码如果用户是在event_add之后修改系统时间,就能发现这个bug。
基本时间操作函数:
Libevent采用的时间类型是struct timeval,这个类型在很多平台都提供了。此外,Libevent还提供了一系列的时间操作函数。比如两个struct timeval相加、相减、比较大小。有些平台直接提供了一些时间操作函数,但有些则没有,那么Libevent就自己实现。这些宏如下:
- #ifdef _EVENT_HAVE_TIMERADD
- #define evutil_timeradd(tvp, uvp, vvp) timeradd((tvp), (uvp), (vvp))
- #define evutil_timersub(tvp, uvp, vvp) timersub((tvp), (uvp), (vvp))
- #else
- #define evutil_timeradd(tvp, uvp, vvp) \
- do { \
- (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \
- (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \
- if ((vvp)->tv_usec >= 1000000) { \
- (vvp)->tv_sec++; \
- (vvp)->tv_usec -= 1000000; \
- } \
- } while (0)
- #define evutil_timersub(tvp, uvp, vvp) \
- do { \
- (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \
- (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \
- if ((vvp)->tv_usec < 0) { \
- (vvp)->tv_sec--; \
- (vvp)->tv_usec += 1000000; \
- } \
- } while (0)
- #endif
-
- #ifdef _EVENT_HAVE_TIMERCLEAR
- #define evutil_timerclear(tvp) timerclear(tvp)
- #else
- #define evutil_timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0
- #endif
-
-
- #define evutil_timercmp(tvp, uvp, cmp) \
- (((tvp)->tv_sec == (uvp)->tv_sec) ? \
- ((tvp)->tv_usec cmp (uvp)->tv_usec) : \
- ((tvp)->tv_sec cmp (uvp)->tv_sec))
-
- #ifdef _EVENT_HAVE_TIMERISSET
- #define evutil_timerisset(tvp) timerisset(tvp)
- #else
- #define evutil_timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec)
- #endif
代码中的那些条件宏,是在配置Libevent的时候检查所在的系统环境而定义的。具体的内容,可以参考《 event-config.h指明所在系统的环境
》一文。
Libevent的时间一般是用在超时event的。对于超时event,用户只需给出一个超时时间,比如多少秒,而不是一个绝对时间。但在Libevent内部,要将这个时间转换成绝对时间。所以在Libevent内部会经常获取系统时间(绝对时间),然后进行一些处理,比如,转换、比较。
cache时间:
Libevent封装了一个evutil_gettimeofday函数用来获取系统时间,该函数在POSIX的系统是直接调用gettimeofday函数,在Windows系统是通过_ftime函数。虽然gettimeofday的 耗时成本不大
,不过Libevent还是使用了一个cache保存时间,使得更加高效。在event_base结构体有一个struct timeval类型的cache变量 tv_cache。处理超时event的两个函数event_add_internal和event_base_loop内部都是调用gettime函数获取时间的。gettime函数如下:
-
- static int
- gettime(struct event_base *base, struct timeval *tp)
- {
- if (base->tv_cache.tv_sec) {
- *tp = base->tv_cache;
- return (0);
- }
-
- …
- }
从上面代码可以看到,Libevent优先使用cache时间。tv_bache变量处理作为cache外,还有另外一个作用,下面会讲到。
cache的时间也是通过调用系统的提供的时间函数得到的。
-
- static inline void
- update_time_cache(struct event_base *base)
- {
- base->tv_cache.tv_sec = 0;
- if (!(base->flags & EVENT_BASE_FLAG_NO_CACHE_TIME))
- gettime(base, &base->tv_cache);
- }
tv_cache是通过调用gettime来获取时间。由于tv_cache.tv_sec已经赋值为0,所以它将使用系统提供的时间函数得到时间。代码也展示了,如果event_base的配置中定义了EVENT_BASE_FLAG_NO_CACHE_TIME宏,将不能使用cache时间。关于这个宏的设置可以参考《配置event_base》一文。
处理用户手动修改系统时间:
如果用户能老老实实,或许代码就不需要写得很复杂。由于用户的不老实,所以有时候要考虑很多很特殊的情况。在Libevent的时间管理这方面也是如此。
Libevent在实际使用时还有一个坑爹的现象,那就是,用户手动把时钟(wall time)往回调了。比如说现在是上午9点,但用户却把OS的系统时间调成了上午7点。这是很坑爹的。对于超时event和event_add的第二个参数,都是一个时间长度。但在内部Libevent要把这个时间转换成绝对时间。
如果用户手动修改了OS的系统时间。那么Libevent把超时时间长度转换成绝对时间将是弄巧成拙。拿上面的时间例子。如果用户设置的超时为1分钟。那么到了9:01就会超时。如果用户把系统时间调成了7点,那么要过2个小时01分才能发生超时。这就和用户原先的设置差得很远了。
读者可能会说,这个责任应该是由用户负。呵呵,但Libevent提供的函数接口是一个时间长度,既然是时间长度,那么无论用户怎么改变OS的系统时间,这个时间长度都是相对于event_add ()被调用的那一刻算起,这是不会变的。如果Libevent做不到这一点,这说明是Libevent没有遵循接口要求。
为此,Libevent提出了一些解决方案。
使用monotonic时间:
问题的由来是因为用户能修改系统时间,所以最简单的解决方案就是能获取到一个用户不能修改的时间,然后以之为绝对时间。因为event_add提供给用户的接口使用的是一个时间长度,所以无论是使用哪个绝对时间都是无所谓的。
基于这一点,Libevent找到了monotonic时间,从字面来看monotonic翻译成单调。我们高中学过的单调函数英文名就是monotonic function。monotonic时间就像单调递增函数那样,只增不减的,没有人能手动修改之。
monotonic时间是boot启动后到现在的时间。用户是不能修改这个时间。如果Libevent所在的系统支持monotonic时间的话,那么Libevent就会选用这个monotonic时间为绝对时间。
首先,Libevent检查所在的系统是否支持monotonic时间。在event_base_new_with_config函数中会调用detect_monotonic函数检测。
-
- static void
- detect_monotonic(void)
- {
- #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
- struct timespec ts;
- static int use_monotonic_initialized = 0;
-
- if (use_monotonic_initialized)
- return;
-
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0)
- use_monotonic = 1;
- use_monotonic_initialized = 1;
- #endif
- }
从上面代码可以看到,如果Libevent所在的系统支持monotonic时间,就将全局变量use_monotonic赋值1,作为标志。
如果Libevent所在的系统支持monotonic时间,那么Libevent将使用monotonic时间,也就是说Libevent用于获取系统时间的函数gettime将由monotonic提供时间。
-
- static int
- gettime(struct event_base *base, struct timeval *tp)
- {
- EVENT_BASE_ASSERT_LOCKED(base);
-
- if (base->tv_cache.tv_sec) {
- *tp = base->tv_cache;
- return (0);
- }
-
- #if defined(_EVENT_HAVE_CLOCK_GETTIME) && defined(CLOCK_MONOTONIC)
- if (use_monotonic) {
- struct timespec ts;
-
- if (clock_gettime(CLOCK_MONOTONIC, &ts) == -1)
- return (-1);
-
- tp->tv_sec = ts.tv_sec;
- tp->tv_usec = ts.tv_nsec / 1000;
-
-
- if (base->last_updated_clock_diff + CLOCK_SYNC_INTERVAL
- < ts.tv_sec) {
- struct timeval tv;
- evutil_gettimeofday(&tv,NULL);
-
- evutil_timersub(&tv, tp, &base->tv_clock_diff);
- base->last_updated_clock_diff = ts.tv_sec;
- }
-
- return (0);
- }
- #endif
-
-
- return (evutil_gettimeofday(tp, NULL));
- }
上面的代码虽然首先是使用cache时间,但实际上event_base结构体的cache时间也是通过调用gettime函数而得到的。上面代码也可以看到:如果所在的系统没有提供monotonic时间,那么就只能使用evutil_gettimeofday这个函数提供的系统时间了。
从上面的分析可知,如果Libevent所在的系统支持monotonic时间,那么根本就不用考虑用户手动修改系统时间这坑爹的事情。但如果所在的系统没有支持monotonic时间,那么Libevent就只能使用evutil_gettimeofday获取一个用户能修改的时间。
尽可能精确记录时间差:
现在来看一下Libevent在这种情况下在怎么解决这个坑爹得的问题。
Libevent给出的方案是,尽可能精确地计算 用户往回调了多长时间。如果知道了用户往回调了多长时间,那么将小根堆中的全部event的时间都往回调一样的时间即可。Libevent调用timeout_correct函数处理这个问题。
-
- static void
- timeout_correct(struct event_base *base, struct timeval *tv)
- {
-
- struct event **pev;
- unsigned int size;
- struct timeval off;
- int i;
-
-
- if (use_monotonic)
- return;
-
-
- gettime(base, tv);
-
-
-
- if (evutil_timercmp(tv, &base->event_tv, >=)) {
- base->event_tv = *tv;
- return;
- }
-
- evutil_timersub(&base->event_tv, tv, &off);
-
- pev = base->timeheap.p;
- size = base->timeheap.n;
-
-
- for (; size-- > 0; ++pev) {
- struct timeval *ev_tv = &(**pev).ev_timeout;
-
-
- evutil_timersub(ev_tv, &off, ev_tv);
- }
-
-
- base->event_tv = *tv;
- }
Libevent用event_base的成员变量event_tv保存用户修改系统时间前的系统时间。如果刚保存完,用户就修改系统时间,这样就能精确地计算出用户往回调了多长时间。但毕竟Libevent是用户态的库,不能做到用户修改系统时间前的一刻保存系统时间。
于是Libevent采用多采点的方式,即时不时就保存一次系统时间。所以在event_base_loop函数中的while循环体里面会有gettime(base, &base->event_tv);这是为了能多采点。但这个while循环里面还会执行多路IO复用函数和处理被激活event的回调函数(这个回调函数执行多久也是个未知数)。这两个函数的执行需要的时间可能会比较长,如果用户刚才是在执行完这两个函数之后修改系统时间,那么event_tv保存的时间就不怎么精确了。这也是没有办法的啊!!唉!!
下面贴出event_base_loop函数
-
- 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;
-
-
- clear_time_cache(base);
-
-
- while (!done) {
- timeout_correct(base, &tv);
-
- tv_p = &tv;
- if (!N_ACTIVE_CALLBACKS(base) && !(flags & EVLOOP_NONBLOCK)) {
-
- timeout_next(base, &tv_p);
- } else {
- evutil_timerclear(&tv);
- }
-
-
- gettime(base, &base->event_tv);
-
-
-
-
-
-
- clear_time_cache(base);
-
-
- res = evsel->dispatch(base, tv_p);
-
-
- update_time_cache(base);
-
-
- timeout_process(base);
-
- if (N_ACTIVE_CALLBACKS(base)) {
- int n = event_process_active(base);
- }
- }
-
- return (retval);
- }
可以看到,在dispatch和event_process_active之间有一个update_time_cache。而前面的gettime(base,&base->event_tv);实际上取的就是cache的时间。所以,如果该Libevent支持cache的话,会精确那么一些。一般来说,用户为event设置的回调函数,不应该执行太久的时间。这也是tv_cache时间的另外一个作用。
出现的bug:
由于Libevent的解决方法并不是很精确,所以还是会有一些bug。下面给出一个bug。如果用户是在调用event_new函数之后,event_add之前对系统时间进行修改,那么无论用户设置的event超时有多长,都会马上触发超时。下面给出实际的例子
。这个例子要运行在不支持
monotonic
时间的系统
,我是在Windows运行的。
- #include <event2/event.h>
- #include<stdio.h>
-
-
- void timeout_cb(int fd, short event, void *arg)
- {
- printf("in the timeout_cb\n");
- }
-
-
- int main()
- {
- struct event_base *base = event_base_new();
-
- struct event *ev = event_new(base, -1, EV_TIMEOUT, timeout_cb, NULL);
-
- int ch;
-
- scanf("%c", &ch);
-
- struct timeval tv = {100, 0};
-
- event_add(ev, &tv);
-
- event_base_dispatch(base);
-
- return 0;
- }
这个bug的出现是因为,在event_base_new_with_config函数中有gettime(base,&base->event_tv),所以event_tv记录了修改前的时间。而event_add是在修改系统时间后才调用的。所以event结构体的ev_timeout变量使用的是修改系统时间后的超时时间,这是正确的时间。在执行timeout_correct函数时,Libevent发现用户修改了系统时间,所以就将本来正确的ev_timeout减去了off。所以ev_timeout就变得比较修改后的系统时间小了。在后面检查超时时,就会发现该event已经超时了(实际是没有超时),就把它触发。
如果该event有EV_PERSIST属性,那么之后的超时则会是正确的。这个留给读者去分析吧。
另外,Libevent并没有考虑把时钟往后调,比如现在是9点,用户把系统时间调成10点。上面的代码如果用户是在event_add之后修改系统时间,就能发现这个bug。