From: Luiz Augusto von Dentz <luiz.dentz@gmail.com>
To: linux-bluetooth@vger.kernel.org
Subject: [PATCH BlueZ v1 17/27] shared/mainloop: Add idle callbacks
Date: Thu, 22 Oct 2026 11:46:50 -0400 [thread overview]
Message-ID: <20261022154701.3959500-18-luiz.dentz@gmail.com> (raw)
In-Reply-To: <20261022154701.3959500-1-luiz.dentz@gmail.com>
From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
Add mainloop_add_idle(), whose callback runs once no file descriptor is
ready, after everything pending was handled, as GLib's idle sources do.
It is armed again with mainloop_modify_timeout() with 0 and removed with
mainloop_remove_timeout(), its id being above the file descriptors.
A timeout of 0 cannot be used for this: mainloop_add_timeout() leaves it
disarmed, and a short timer would run before pending events.
Assisted-by: Claude:claude-opus-5-5
---
src/shared/mainloop-ell.c | 6 ++
src/shared/mainloop-glib.c | 6 ++
src/shared/mainloop.c | 156 ++++++++++++++++++++++++++++++++++++-
src/shared/mainloop.h | 2 +
4 files changed, 169 insertions(+), 1 deletion(-)
diff --git a/src/shared/mainloop-ell.c b/src/shared/mainloop-ell.c
index 1aec7fad9e96..1dda9c9ef761 100644
--- a/src/shared/mainloop-ell.c
+++ b/src/shared/mainloop-ell.c
@@ -100,6 +100,12 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return -ENOSYS;
}
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop-glib.c b/src/shared/mainloop-glib.c
index 042119baf1ad..971562610e02 100644
--- a/src/shared/mainloop-glib.c
+++ b/src/shared/mainloop-glib.c
@@ -94,6 +94,12 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return -ENOSYS;
}
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ return -EOPNOTSUPP;
+}
+
int mainloop_modify_timeout(int fd, unsigned int msec)
{
return -ENOSYS;
diff --git a/src/shared/mainloop.c b/src/shared/mainloop.c
index b3ec0f2b1773..652436be944d 100644
--- a/src/shared/mainloop.c
+++ b/src/shared/mainloop.c
@@ -19,6 +19,7 @@
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
+#include <stdbool.h>
#include <string.h>
#include <signal.h>
#include <sys/signalfd.h>
@@ -88,6 +89,10 @@ void mainloop_exit_failure(void)
epoll_terminate = 1;
}
+static bool idle_pending(void);
+static void run_idles(void);
+static void remove_all_idles(void);
+
int mainloop_run(void)
{
unsigned int i;
@@ -96,10 +101,16 @@ int mainloop_run(void)
struct epoll_event events[MAX_EPOLL_EVENTS];
int n, nfds;
- nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS, -1);
+ nfds = epoll_wait(epoll_fd, events, MAX_EPOLL_EVENTS,
+ idle_pending() ? 0 : -1);
if (nfds < 0)
continue;
+ if (!nfds) {
+ run_idles();
+ continue;
+ }
+
for (n = 0; n < nfds; n++) {
uint64_t id = events[n].data.u64;
int fd = id % MAX_MAINLOOP_ENTRIES;
@@ -129,6 +140,8 @@ int mainloop_run(void)
}
}
+ remove_all_idles();
+
close(epoll_fd);
epoll_fd = 0;
@@ -309,8 +322,138 @@ int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
return data->fd;
}
+/* Idle callbacks run when no file descriptor is ready, after everything
+ * pending was handled, as with GLib. Their ids are above the file
+ * descriptors, so the timeout functions tell them apart.
+ */
+#define IDLE_ID_BASE 0x10000
+
+struct idle_data {
+ int id;
+ bool armed;
+ bool removed;
+ mainloop_timeout_func callback;
+ mainloop_destroy_func destroy;
+ void *user_data;
+ struct idle_data *next;
+};
+
+static struct idle_data *idle_list;
+static int idle_next_id = IDLE_ID_BASE;
+
+static struct idle_data *find_idle(int id)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->id == id && !idle->removed)
+ return idle;
+ }
+
+ return NULL;
+}
+
+static bool idle_pending(void)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->armed && !idle->removed)
+ return true;
+ }
+
+ return false;
+}
+
+static void free_removed_idles(void)
+{
+ struct idle_data **prev = &idle_list;
+
+ while (*prev) {
+ struct idle_data *idle = *prev;
+
+ if (!idle->removed) {
+ prev = &idle->next;
+ continue;
+ }
+
+ *prev = idle->next;
+
+ if (idle->destroy)
+ idle->destroy(idle->user_data);
+
+ free(idle);
+ }
+}
+
+static void remove_all_idles(void)
+{
+ struct idle_data *idle;
+
+ for (idle = idle_list; idle; idle = idle->next)
+ idle->removed = true;
+
+ free_removed_idles();
+}
+
+/* Run the idle callbacks armed before this pass, once each */
+static void run_idles(void)
+{
+ struct idle_data *idle;
+ int last = idle_next_id;
+
+ for (idle = idle_list; idle; idle = idle->next) {
+ if (idle->removed || !idle->armed || idle->id >= last)
+ continue;
+
+ idle->armed = false;
+ idle->callback(idle->id, idle->user_data);
+ }
+
+ free_removed_idles();
+}
+
+/* Run callback once the mainloop is idle, again after
+ * mainloop_modify_timeout() with 0; removed with mainloop_remove_timeout().
+ */
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy)
+{
+ struct idle_data *idle, **last;
+
+ if (!callback)
+ return -EINVAL;
+
+ idle = malloc(sizeof(*idle));
+ if (!idle)
+ return -ENOMEM;
+
+ memset(idle, 0, sizeof(*idle));
+ idle->id = idle_next_id++;
+ idle->armed = true;
+ idle->callback = callback;
+ idle->destroy = destroy;
+ idle->user_data = user_data;
+
+ for (last = &idle_list; *last; last = &(*last)->next)
+ ;
+ *last = idle;
+
+ return idle->id;
+}
+
int mainloop_modify_timeout(int id, unsigned int msec)
{
+ if (id >= IDLE_ID_BASE) {
+ struct idle_data *idle = find_idle(id);
+
+ if (!idle)
+ return -ENOENT;
+
+ idle->armed = true;
+ return 0;
+ }
+
if (msec > 0) {
if (timeout_set(id, msec) < 0)
return -EIO;
@@ -324,5 +467,16 @@ int mainloop_modify_timeout(int id, unsigned int msec)
int mainloop_remove_timeout(int id)
{
+ if (id >= IDLE_ID_BASE) {
+ struct idle_data *idle = find_idle(id);
+
+ if (!idle)
+ return -ENOENT;
+
+ /* Freed after the current pass, which may be running it */
+ idle->removed = true;
+ return 0;
+ }
+
return mainloop_remove_fd(id);
}
diff --git a/src/shared/mainloop.h b/src/shared/mainloop.h
index fb02dd34109b..2dd07ce03818 100644
--- a/src/shared/mainloop.h
+++ b/src/shared/mainloop.h
@@ -33,6 +33,8 @@ int mainloop_remove_fd(int fd);
int mainloop_add_timeout(unsigned int msec, mainloop_timeout_func callback,
void *user_data, mainloop_destroy_func destroy);
int mainloop_modify_timeout(int fd, unsigned int msec);
+int mainloop_add_idle(mainloop_timeout_func callback, void *user_data,
+ mainloop_destroy_func destroy);
int mainloop_remove_timeout(int id);
int mainloop_set_signal(sigset_t *mask, mainloop_signal_func callback,
--
2.56.0
next prev parent reply other threads:[~2026-10-22 15:47 UTC|newest]
Thread overview: 30+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 15:46 [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 01/27] tools/test-runner: Do not report filesystems already mounted Luiz Augusto von Dentz
2026-10-22 20:02 ` Run the testers under User Mode Linux with time travel bluez.test.bot
2026-10-22 15:46 ` [PATCH BlueZ v1 02/27] shared/tester: Time out teardown Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 03/27] mgmt-tester: Wait for LL Privacy setup to complete Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 04/27] iso-tester: Fix leaking socket watches Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 05/27] l2cap-tester: Fix race in LE Client - Close socket 2 Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 06/27] iso-tester: Fix busy waiting in PAST tests Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 07/27] doc: Enable IPv6 in tester.config Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 08/27] 6lowpan-tester: Find the interface of the controller Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 09/27] emulator: Remove -T option from btvirt Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 10/27] doc: Add btvirt documentation Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 11/27] emulator: Add vhost-user backend Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 12/27] emulator: Add vhost-user virtio server to btvirt Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 13/27] emulator: Add time travel calendar " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 14/27] emulator: Add virtio console and time travel client " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 15/27] tools/test-runner: Add User Mode Linux support Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 16/27] doc: Add virtio documentation Luiz Augusto von Dentz
2026-10-22 15:46 ` Luiz Augusto von Dentz [this message]
2026-10-22 15:46 ` [PATCH BlueZ v1 18/27] shared/timeout: Run timeouts of 0 once idle Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 19/27] shared/tester: Remove GLib dependency Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 20/27] emulator/hciemu: " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 21/27] tools: Build testers that do not need GLib with the mainloop Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 22/27] shared/mainloop: Add deferred callbacks Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 23/27] tools: Add socket watches for testers without GLib Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 24/27] tools: Run sco-tester, rfcomm-tester and 6lowpan-tester on the mainloop Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 25/27] tools: Run l2cap-tester and iso-tester " Luiz Augusto von Dentz
2026-10-22 15:46 ` [PATCH BlueZ v1 26/27] doc/virtio: Update measurements of the testers Luiz Augusto von Dentz
2026-10-22 15:47 ` [PATCH BlueZ v1 27/27] doc: Split tester.config for QEMU and User Mode Linux Luiz Augusto von Dentz
2026-10-29 18:40 ` [PATCH BlueZ v1 00/27] Run the testers under User Mode Linux with time travel patchwork-bot+bluetooth
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=20261022154701.3959500-18-luiz.dentz@gmail.com \
--to=luiz.dentz@gmail.com \
--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