diff --git a/event.c b/event.c index f13cdcf1..df104554 100644 --- a/event.c +++ b/event.c @@ -1258,12 +1258,12 @@ event_persist_closure(struct event_base *base, struct event *ev) * ev_io_timeout after the last time it was _scheduled_ for, * not ev_io_timeout after _now_. If it fired for another * reason, though, the timeout ought to start ticking _now_. */ - struct timeval run_at; + struct timeval run_at, relative_to, delay, now; + ev_uint32_t usec_mask = 0; EVUTIL_ASSERT(is_same_common_timeout(&ev->ev_timeout, &ev->ev_io_timeout)); + gettime(base, &now); if (is_common_timeout(&ev->ev_timeout, base)) { - ev_uint32_t usec_mask; - struct timeval delay, relative_to; delay = ev->ev_io_timeout; usec_mask = delay.tv_usec & ~MICROSECONDS_MASK; delay.tv_usec &= MICROSECONDS_MASK; @@ -1271,20 +1271,26 @@ event_persist_closure(struct event_base *base, struct event *ev) relative_to = ev->ev_timeout; relative_to.tv_usec &= MICROSECONDS_MASK; } else { - gettime(base, &relative_to); + relative_to = now; } - evutil_timeradd(&relative_to, &delay, &run_at); - run_at.tv_usec |= usec_mask; } else { - struct timeval relative_to; + delay = ev->ev_io_timeout; if (ev->ev_res & EV_TIMEOUT) { relative_to = ev->ev_timeout; } else { - gettime(base, &relative_to); + relative_to = now; } - evutil_timeradd(&ev->ev_io_timeout, &relative_to, - &run_at); } + 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),