From dfd808cbad491bdb6610e9050a3f98dd49d42e84 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 19 Apr 2012 00:25:12 -0400 Subject: [PATCH] If time has jumped so we'd reschedule a periodic event in the past, schedule it for the future instead Fixes an issue reported on libevent-users in the thread "a dead looping bug when changing system time backward". Previously, if time jumped forward 1 hour[*] and we had a one-second periodic timer event, that event would get invoked 3600 times. That's almost certainly not what anybody wants. In a future version of Libevent, we should expose the amount of time that the callbac kwould have been invoked somehow. [*] Forward time jumps can happen with nonmonotonic clocks, or with clocks that jump on suspend/resume. It can also happen from Libevent's point of view if the user exits from event_base_loop() and doesn't call it again for a while. --- event.c | 10 +++++++++- test/regress.c | 23 ++++++++++++++++++++++- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/event.c b/event.c index 3adbc089..df104554 100644 --- a/event.c +++ b/event.c @@ -1280,9 +1280,17 @@ event_persist_closure(struct event_base *base, struct event *ev) } else { relative_to = now; } - } evutil_timeradd(&relative_to, &delay, &run_at); + if (evutil_timercmp(&run_at, &now, <)) { + /* Looks like we missed at least one invocation due to + * a clock jump, not running the event loop for a + * while, really slow callbacks, or + * something. Reschedule relative to now. + */ + evutil_timeradd(&now, &delay, &run_at); + } + run_at.tv_usec |= usec_mask; event_add_internal(ev, &run_at, 1); } EVBASE_RELEASE_LOCK(base, th_base_lock); diff --git a/test/regress.c b/test/regress.c index 2d48583d..3d97d0b2 100644 --- a/test/regress.c +++ b/test/regress.c @@ -627,6 +627,27 @@ test_persistent_timeout(void) event_del(&ev); } +static void +test_persistent_timeout_jump(void *ptr) +{ + struct basic_test_data *data = ptr; + struct event ev; + int count = 0; + struct timeval msec100 = { 0, 100 * 1000 }; + struct timeval msec50 = { 0, 50 * 1000 }; + + event_assign(&ev, data->base, -1, EV_PERSIST, periodic_timeout_cb, &count); + event_add(&ev, &msec100); + /* Wait for a bit */ + sleep(1); + event_base_loopexit(data->base, &msec50); + event_base_dispatch(data->base); + tt_int_op(count, ==, 1); + +end: + event_del(&ev); +} + struct persist_active_timeout_called { int n; short events[16]; @@ -2338,8 +2359,8 @@ struct testcase_t main_testcases[] = { BASIC(bad_assign, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), BASIC(bad_reentrant, TT_FORK|TT_NEED_BASE|TT_NO_LOGS), - /* These are still using the old API */ LEGACY(persistent_timeout, TT_FORK|TT_NEED_BASE), + { "persistent_timeout_jump", test_persistent_timeout_jump, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, { "persistent_active_timeout", test_persistent_active_timeout, TT_FORK|TT_NEED_BASE, &basic_setup, NULL }, LEGACY(priorities, TT_FORK|TT_NEED_BASE), -- 2.40.0