From a0d941e411820e6d49a528757f53fd0f6ce53f3e Mon Sep 17 00:00:00 2001 From: Manuel Stoeckl Date: Fri, 3 Jan 2020 14:25:04 -0500 Subject: [PATCH] event-loop-test: Verify proper timer cancellation The implementation of timer event sources based on timerfds ensured specific edge-case behavior with regards to removing and updating timers: Calls to `wl_event_loop_dispatch` will dispatch all timer event sources that have expired up to that point, with one exception. When multiple timer event sources are due to be dispatched in a single call of `wl_event_loop_dispatch`, calling wl_event_source_remove` from within a timer event source callback will prevent the removed event source's callback from being called. Note that disarming or updating one of the later timers that is due to be dispatched, from within a timer callback, will NOT prevent that timer's callback from being invoked by `wl_event_loop_dispatch`. This commit adds a test that verifies the above behavior. (Because epoll_wait is not documented to return timerfds in chronological order, (although it does, in practice), the test code does not depend on the order in which timers are dispatched.) Signed-off-by: Manuel Stoeckl --- tests/event-loop-test.c | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/tests/event-loop-test.c b/tests/event-loop-test.c index 9659815..3002e6d 100644 --- a/tests/event-loop-test.c +++ b/tests/event-loop-test.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include "wayland-private.h" @@ -330,6 +331,80 @@ TEST(event_loop_timer_updates) wl_event_loop_destroy(loop); } +struct timer_cancel_context { + struct wl_event_source *timers[4]; + struct timer_cancel_context *back_refs[4]; + int order[4]; + int called, first; +}; + +static int +timer_cancel_callback(void *data) { + struct timer_cancel_context **context_ref = data; + struct timer_cancel_context *context = *context_ref; + int i = (int)(context_ref - context->back_refs); + + context->called++; + context->order[i] = context->called; + + if (context->called == 1) { + context->first = i; + /* Removing a timer always prevents its callback from + * being called ... */ + wl_event_source_remove(context->timers[(i + 1) % 4]); + /* ... but disarming or rescheduling a timer does not, + * (in the case where the modified timers had already expired + * as of when `wl_event_loop_dispatch` was called.) */ + assert(wl_event_source_timer_update(context->timers[(i + 2) % 4], + 0) == 0); + assert(wl_event_source_timer_update(context->timers[(i + 3) % 4], + 2000000000) == 0); + } + + return 0; +} + +TEST(event_loop_timer_cancellation) +{ + struct wl_event_loop *loop = wl_event_loop_create(); + struct timer_cancel_context context; + int i; + + memset(&context, 0, sizeof(context)); + + /* Test that when multiple timers are dispatched in a single call + * of `wl_event_loop_dispatch`, that having some timers run code + * to modify the other timers only actually prevents the other timers + * from running their callbacks when the those timers are removed, not + * when they are disarmed or rescheduled. */ + + for (i = 0; i < 4; i++) { + context.back_refs[i] = &context; + context.timers[i] = + wl_event_loop_add_timer(loop, timer_cancel_callback, + &context.back_refs[i]); + assert(context.timers[i]); + + assert(wl_event_source_timer_update(context.timers[i], 1) == 0); + } + + usleep(MSEC_TO_USEC(2)); + assert(wl_event_loop_dispatch(loop, 0) == 0); + + /* Tracking which timer was first makes this test independent of the + * actual timer dispatch order, which is not guaranteed by the docs */ + assert(context.order[context.first] == 1); + assert(context.order[(context.first + 1) % 4] == 0); + assert(context.order[(context.first + 2) % 4] > 1); + assert(context.order[(context.first + 3) % 4] > 1); + + wl_event_source_remove(context.timers[context.first]); + wl_event_source_remove(context.timers[(context.first + 2) % 4]); + wl_event_source_remove(context.timers[(context.first + 3) % 4]); + + wl_event_loop_destroy(loop); +} + struct event_loop_destroy_listener { struct wl_listener listener; int done; -- 2.7.4