Commit a0d941e4 authored by Manuel Stoeckl's avatar Manuel Stoeckl Committed by Simon Ser
Browse files

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: default avatarManuel Stoeckl <code@mstoeckl.com>
parent fab3cb3b
......@@ -29,6 +29,7 @@
#include <assert.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/time.h>
#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;
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment