From aedd863e6f66fd446227e9b107bc9c1970092634 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Mon, 12 Oct 2026 13:36:04 +0300 Subject: timers: drop retired surface timer ids yasl_surface_del_timer zeroed its entry and kept it. A one-shot surface timer that fired stayed in the surface's id list until the surface was destroyed. A client that re-arms a one-shot timer, such as a cursor blink, grew the list without bound, and each delete scanned all of it. Delete now removes the entry. Before the list grows, add drops the ids that the loop has retired. The list holds only armed timers. Delete now returns false for a timer that already retired, as yasl_loop_del_timer does. Co-Authored-By: Claude Opus 5.5 --- src/common/backend.c | 16 ++++++++++++---- src/common/loop.c | 9 +++++++++ src/common/loop.h | 2 ++ tests/common/test_timers.c | 11 +++++++++++ 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/common/backend.c b/src/common/backend.c index ad4d908..4c755bd 100644 --- a/src/common/backend.c +++ b/src/common/backend.c @@ -1,4 +1,5 @@ #include "backend.h" +#include "loop.h" #include #include @@ -369,6 +370,15 @@ yasl_timer_id yasl_surface_add_timer(struct yasl_surface *s, uint64_t interval_n yasl_timer_id id = yasl_loop_add_timer(s->display->loop, interval_ns, cb, ud); if (!id) return 0; + /* A one-shot timer retires itself in the loop. Drop retired ids before + * the list grows, so the list holds only armed timers. */ + if (s->timer_count >= s->timer_capacity) { + int kept = 0; + for (int i = 0; i < s->timer_count; i++) + if (yasl_loop_timer_armed(s->display->loop, s->timers[i])) + s->timers[kept++] = s->timers[i]; + s->timer_count = kept; + } if (s->timer_count >= s->timer_capacity) { int new_cap = s->timer_capacity ? s->timer_capacity * 2 : 4; yasl_timer_id *nt = realloc(s->timers, (size_t)new_cap * sizeof(*nt)); @@ -388,10 +398,8 @@ bool yasl_surface_del_timer(struct yasl_surface *s, yasl_timer_id id) if (!s || id == 0) return false; for (int i = 0; i < s->timer_count; i++) { if (s->timers[i] == id) { - s->timers[i] = 0; - if (s->display && s->display->loop) - yasl_loop_del_timer(s->display->loop, id); - return true; + s->timers[i] = s->timers[--s->timer_count]; + return s->display && yasl_loop_del_timer(s->display->loop, id); } } return false; diff --git a/src/common/loop.c b/src/common/loop.c index 3815e91..a63df16 100644 --- a/src/common/loop.c +++ b/src/common/loop.c @@ -208,6 +208,15 @@ static void timer_cleanup_deleted(struct yasl_loop *l) l->timer_count = dest; } +bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id) +{ + if (!l || id == 0) return false; + for (int i = 0; i < l->timer_count; i++) + if (l->timers[i].id == id) + return !l->timers[i].deleted; + return false; +} + int yasl_loop_next_timeout_ms(const struct yasl_loop *l) { if (!l || l->timer_count <= 0) return -1; diff --git a/src/common/loop.h b/src/common/loop.h index 579fa2c..663417a 100644 --- a/src/common/loop.h +++ b/src/common/loop.h @@ -4,5 +4,7 @@ #include "yasl/loop.h" int yasl_loop_next_timeout_ms(const struct yasl_loop *l); +/* Whether id is armed: added, not deleted, and not retired by its callback. */ +bool yasl_loop_timer_armed(const struct yasl_loop *l, yasl_timer_id id); #endif /* YASL_COMMON_LOOP_H */ diff --git a/tests/common/test_timers.c b/tests/common/test_timers.c index d02bfe9..4e95c5a 100644 --- a/tests/common/test_timers.c +++ b/tests/common/test_timers.c @@ -264,6 +264,17 @@ static void test_surface_timers(void) yasl_display_dispatch(d, 10); assert(sctx.count == count_before); + /* A fired one-shot surface timer is retired, as a loop timer is. Many + * of them stay retired after the surface's id list compacts. */ + for (int i = 0; i < 64; i++) { + struct test_ctx octx = {0}; + octx.id = yasl_surface_add_timer(s, 1000000ull, oneshot_cb, &octx); + assert(octx.id > 0); + while (octx.count == 0) + yasl_display_dispatch(d, 50); + assert(!yasl_surface_del_timer(s, octx.id)); + } + /* Surface timer auto-cleanup on surface destruction */ struct test_ctx sctx2 = {0}; sctx2.id = yasl_surface_add_timer(s, 2000000ull, surface_timer_repeat_cb, &sctx2); -- cgit v1.2.3