From: Kyle Pepper <cochcoder@proton.me>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH] shared/timeout: Keep callback data alive during removal
Date: Fri, 06 Nov 2026 04:23:57 +0000 [thread overview]
Message-ID: <20261106042339.811655-1-cochcoder@proton.me> (raw)
A timeout callback can remove its own timer. The mainloop then destroys
the wrapper before timeout_callback reads its identifier again.
Hold a reference while dispatching and clear the identifier when the
timer is destroyed. This also prevents a removed callback from modifying
a replacement timer that reused its descriptor. Keep user destruction
synchronous for timerfd timers.
Add unit coverage for idle and timerfd timers with one-shot, repeating,
self-removing and replacement callbacks.
Fixes: 1f11ee8f5194 ("shared: Add timeout handling with mainloop support")
Assisted-by: Codex:GPT-6
---
src/shared/timeout-mainloop.c | 26 +++++++--
unit/test-mainloop.c | 100 ++++++++++++++++++++++++++++++++++
2 files changed, 121 insertions(+), 5 deletions(-)
diff --git a/src/shared/timeout-mainloop.c b/src/shared/timeout-mainloop.c
index e6f9c5e826..edcc801114 100644
--- a/src/shared/timeout-mainloop.c
+++ b/src/shared/timeout-mainloop.c
@@ -16,31 +16,46 @@
struct timeout_data {
int id;
+ unsigned int ref_count;
timeout_func_t func;
timeout_destroy_func_t destroy;
unsigned int timeout;
void *user_data;
};
+static void timeout_unref(struct timeout_data *data)
+{
+ if (!--data->ref_count)
+ free(data);
+}
+
static void timeout_callback(int id, void *user_data)
{
struct timeout_data *data = user_data;
+ bool repeat;
- if (data->func(data->user_data) &&
- !mainloop_modify_timeout(data->id, data->timeout))
- return;
+ /* The callback may remove the timeout. */
+ data->ref_count++;
+ repeat = data->func(data->user_data);
+
+ if (data->id) {
+ if (!repeat || mainloop_modify_timeout(data->id, data->timeout))
+ mainloop_remove_timeout(data->id);
+ }
- mainloop_remove_timeout(data->id);
+ timeout_unref(data);
}
static void timeout_destroy(void *user_data)
{
struct timeout_data *data = user_data;
+ data->id = 0;
+
if (data->destroy)
data->destroy(data->user_data);
- free(data);
+ timeout_unref(data);
}
unsigned int timeout_add(unsigned int timeout, timeout_func_t func,
@@ -49,6 +64,7 @@ unsigned int timeout_add(unsigned int timeout, timeout_func_t func,
struct timeout_data *data;
data = new0(struct timeout_data, 1);
+ data->ref_count = 1;
data->func = func;
data->user_data = user_data;
data->timeout = timeout;
diff --git a/unit/test-mainloop.c b/unit/test-mainloop.c
index 70ee03ee42..4cff654147 100644
--- a/unit/test-mainloop.c
+++ b/unit/test-mainloop.c
@@ -12,6 +12,7 @@
#include <stdlib.h>
#include "src/shared/mainloop.h"
+#include "src/shared/timeout.h"
#define test_failed_msg(msg) \
@@ -153,6 +154,104 @@ static void test_timeout(struct test_data *data)
check(data->destroys == 1);
}
+struct wrapper_data {
+ unsigned int id;
+ unsigned int calls;
+ unsigned int destroys;
+ unsigned int replacement_calls;
+ unsigned int replacement_destroys;
+ unsigned int interval;
+ bool repeat;
+ bool remove;
+ bool replace;
+};
+
+static void wrapper_destroy(void *user_data)
+{
+ struct wrapper_data *data = user_data;
+
+ data->destroys++;
+}
+
+static void replacement_destroy(void *user_data)
+{
+ struct wrapper_data *data = user_data;
+
+ data->replacement_destroys++;
+}
+
+static bool replacement_callback(void *user_data)
+{
+ struct wrapper_data *data = user_data;
+
+ data->replacement_calls++;
+ mainloop_exit_success();
+ return false;
+}
+
+static bool wrapper_callback(void *user_data)
+{
+ struct wrapper_data *data = user_data;
+ unsigned int replacement;
+
+ data->calls++;
+
+ if (data->remove) {
+ timeout_remove(data->id);
+
+ if (data->interval)
+ check(data->destroys == 1);
+
+ if (data->replace) {
+ replacement = timeout_add(1, replacement_callback,
+ data, replacement_destroy);
+ check(replacement > 0);
+
+ if (data->interval)
+ check(replacement == data->id);
+ } else {
+ mainloop_exit_success();
+ }
+
+ return data->repeat;
+ }
+
+ if (data->repeat && data->calls < 3)
+ return true;
+
+ mainloop_exit_success();
+ return false;
+}
+
+static void test_timeout_wrapper(void)
+{
+ unsigned int interval, mode;
+
+ for (interval = 0; interval < 2; interval++) {
+ for (mode = 0; mode < 6; mode++) {
+ struct wrapper_data data = {
+ .interval = interval,
+ .repeat = mode & 1,
+ .remove = mode >= 2,
+ .replace = mode >= 4,
+ };
+
+ mainloop_init();
+ data.id = timeout_add(interval, wrapper_callback,
+ &data, wrapper_destroy);
+ check(data.id > 0);
+ check(mainloop_run() == EXIT_SUCCESS);
+ check(data.calls ==
+ (!data.remove && data.repeat ? 3 : 1));
+ check(data.destroys == 1);
+ check(data.replacement_calls == data.replace);
+ check(data.replacement_destroys == data.replace);
+ fprintf(stderr, "Timeout wrapper %u/%u passed\n",
+ interval, mode);
+ }
+ }
+}
+
#define define_test(name, function) \
do { \
static struct test_data data = {}; \
@@ -167,5 +266,6 @@ int main(int argc, char *argv[])
define_test("Test Modify File Descriptor", test_modify_fd);
define_test("Test Re-Add Other Callback", test_readd_other_callback);
define_test("Test Timeout", test_timeout);
+ test_timeout_wrapper();
return 0;
}
base-commit: 34b6f8e0e998728ce9e8e64e280d9d390c511804
--
2.56.0
next reply other threads:[~2026-11-06 4:24 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-06 4:23 Kyle Pepper [this message]
2026-11-06 11:04 ` shared/timeout: Keep callback data alive during removal bluez.test.bot
2026-11-06 18:30 ` [PATCH] " Luiz Augusto von Dentz
2026-11-06 18:32 ` Luiz Augusto von Dentz
2026-11-06 21:25 ` cochcoder
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261106042339.811655-1-cochcoder@proton.me \
--to=cochcoder@proton.me \
--cc=linux-bluetooth@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox