Linux bluetooth development
 help / color / mirror / Atom feed
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


  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