Hi Pauli, On Sun, Feb 9, 2025 at 5:46 AM Pauli Virtanen <pav@xxxxxx> wrote: > > Add special implementation of fd watcher GSource for audio use. > > For audio use cases, sound server may turn on TX timestamping on a > socket that we are watching. In this case, we shall not consider the TX > timestamping POLLERR as a socket error condition, nor read the TX > timestamps. > > When TX timestamps appear in errqueue, switch from fd poll wait to > polling the fd at regular intervals. This is because unread errqueue > causes poll() to wake up immediately, so the mainloop cannot block on > that, and we have to use a timer instead with some reasonable timeout > for the use case. > > This rate limits wakeups on new TX timestamps we aren't going to read, > and also avoids the busy looping if timestamping was left on but > errqueue is not flushed. > > Implement this only for io-glib; it is only needed for audio use cases > that anyway are using glib. Uses features from GLib 2.36 (from 2013) so > update configure.ac also. > --- > Makefile.am | 1 + > acinclude.m4 | 3 +- > configure.ac | 2 +- > src/shared/io-glib.c | 157 ++++++++++++++++++++++++++++++++++++++++++- > src/shared/io-glib.h | 20 ++++++ > 5 files changed, 179 insertions(+), 4 deletions(-) > create mode 100644 src/shared/io-glib.h > > diff --git a/Makefile.am b/Makefile.am > index 0821530e6..f958e2b99 100644 > --- a/Makefile.am > +++ b/Makefile.am > @@ -253,6 +253,7 @@ shared_sources += src/shared/shell.c src/shared/shell.h > endif > > src_libshared_glib_la_SOURCES = $(shared_sources) \ > + src/shared/io-glib.h \ > src/shared/io-glib.c \ > src/shared/timeout-glib.c \ > src/shared/mainloop-glib.c \ > diff --git a/acinclude.m4 b/acinclude.m4 > index 168117840..598986d6e 100644 > --- a/acinclude.m4 > +++ b/acinclude.m4 > @@ -63,8 +63,7 @@ AC_DEFUN([COMPILER_FLAGS], [ > with_cflags="$with_cflags -Wformat -Wformat-security" > with_cflags="$with_cflags -Wstringop-overflow" > with_cflags="$with_cflags -DG_DISABLE_DEPRECATED" > - with_cflags="$with_cflags -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_28" > - with_cflags="$with_cflags -DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_32" > + with_cflags="$with_cflags -DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_36" > fi > AC_SUBST([WARNING_CFLAGS], $with_cflags) > ]) > diff --git a/configure.ac b/configure.ac > index 75841e4c9..d2b0bab2f 100644 > --- a/configure.ac > +++ b/configure.ac > @@ -81,7 +81,7 @@ AC_CHECK_DECLS([basename], [], > ]) > > > -PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.28) > +PKG_CHECK_MODULES(GLIB, glib-2.0 >= 2.36) I hope this doesn't come with other surprises though, afaik glib had the bad habit of adding its own set of dependencies which made us stick to such old versions. > > if (test "${enable_threads}" = "yes"); then > AC_DEFINE(NEED_THREADS, 1, [Define if threading support is required]) > diff --git a/src/shared/io-glib.c b/src/shared/io-glib.c > index 754043db1..bea9b2c32 100644 > --- a/src/shared/io-glib.c > +++ b/src/shared/io-glib.c > @@ -13,10 +13,14 @@ > #endif > > #include <errno.h> > +#include <sys/socket.h> > > #include <glib.h> > > #include "src/shared/io.h" > +#include "src/shared/io-glib.h" > + > +#define IO_ERR_WATCH_RATELIMIT (500 * G_TIME_SPAN_MILLISECOND) > > struct io_watch { > struct io *io; > @@ -29,11 +33,19 @@ struct io_watch { > struct io { > int ref_count; > GIOChannel *channel; > + bool err_watch; > struct io_watch *read_watch; > struct io_watch *write_watch; > struct io_watch *disconnect_watch; > }; > > +struct io_err_watch { > + GSource source; > + GIOChannel *io; > + GIOCondition events; > + gpointer tag; > +}; > + > static struct io *io_ref(struct io *io) > { > if (!io) > @@ -179,10 +191,17 @@ static struct io_watch *watch_new(struct io *io, GIOCondition cond, > > prio = cond == G_IO_HUP ? G_PRIORITY_DEFAULT_IDLE : G_PRIORITY_DEFAULT; > > - watch->id = g_io_add_watch_full(io->channel, prio, > + if (!io->err_watch) > + watch->id = g_io_add_watch_full(io->channel, prio, > + cond | G_IO_ERR | G_IO_NVAL, > + watch_callback, watch, > + watch_destroy); > + else > + watch->id = io_glib_add_err_watch_full(io->channel, prio, > cond | G_IO_ERR | G_IO_NVAL, > watch_callback, watch, > watch_destroy); > + > if (watch->id == 0) { > watch_destroy(watch); > return NULL; > @@ -250,6 +269,15 @@ bool io_set_disconnect_handler(struct io *io, io_callback_func_t callback, > return io_set_handler(io, G_IO_HUP, callback, user_data, destroy); > } > > +bool io_set_use_err_watch(struct io *io, bool err_watch) > +{ > + if (!io) > + return false; > + > + io->err_watch = err_watch; > + return true; > +} > + > ssize_t io_send(struct io *io, const struct iovec *iov, int iovcnt) > { > int fd; > @@ -278,3 +306,130 @@ bool io_shutdown(struct io *io) > return g_io_channel_shutdown(io->channel, TRUE, NULL) > == G_IO_STATUS_NORMAL; > } > + > +/* > + * GSource implementation that tolerates non-empty MSG_ERRQUEUE, without > + * attempting to flush it. This is intended for use with TX timestamping in > + * cases where someone else is reading the timestamps and we are only interested > + * in POLLHUP or socket errors. > + */ > + > +static gint64 io_err_watch_wakeup; > + > +static gboolean io_err_watch_dispatch(GSource *source, > + GSourceFunc callback, gpointer user_data) > +{ > + struct io_err_watch *watch = (void *)source; > + const GIOFunc func = (void *)callback; > + const gint64 timeout = IO_ERR_WATCH_RATELIMIT; > + GIOCondition cond; > + int fd; > + > + if (!func) > + return FALSE; > + > + fd = g_io_channel_unix_get_fd(watch->io); > + > + /* > + * If woken up by POLLERR only, and SO_ERROR is not set, ignore this > + * event. Also disable polling for some time so that we don't consume > + * too much CPU on events we are not interested in, or busy loop if > + * nobody is flushing the errqueue. > + */ Not sure if I asked about this before, but have you consider disabling POLLERR completely in case we detect the errqueue is in use? Because even with rate limit I think we may still impact the system, the only problem is then if by disabling POLLERR we would get in trouble detecting disconnections? It shall result in POLLHUP though, so if we only care about it for disconnection I assume it would be sufficient. > + if (watch->tag) > + cond = g_source_query_unix_fd(&watch->source, watch->tag); > + else > + cond = 0; > + > + if (cond == G_IO_ERR) { > + int err, ret; > + socklen_t len = sizeof(err); > + > + ret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len); > + if (ret == 0 && err == 0) { > + g_source_remove_unix_fd(&watch->source, watch->tag); > + watch->tag = NULL; > + > + /* io_err watches all wake up at the same time */ > + if (!io_err_watch_wakeup) > + io_err_watch_wakeup = g_get_monotonic_time() > + + timeout; > + > + g_source_set_ready_time(&watch->source, > + io_err_watch_wakeup); > + return TRUE; > + } > + } > + > + if (g_source_get_ready_time(&watch->source) != -1) { > + g_assert(!watch->tag); > + io_err_watch_wakeup = 0; > + watch->tag = g_source_add_unix_fd(&watch->source, fd, > + watch->events); > + g_source_set_ready_time(&watch->source, -1); > + } > + > + cond &= watch->events; > + > + if (cond) > + return func(watch->io, cond, user_data); > + else > + return TRUE; > +} > + > +static void io_err_watch_finalize(GSource *source) > +{ > + struct io_err_watch *watch = (void *)source; > + > + if (watch->tag) > + g_source_remove_unix_fd(&watch->source, watch->tag); > + > + g_io_channel_unref(watch->io); > +} > + > +guint io_glib_add_err_watch_full(GIOChannel *io, gint priority, > + GIOCondition events, > + GIOFunc func, gpointer user_data, > + GDestroyNotify notify) > +{ > + static GSourceFuncs source_funcs = { > + .dispatch = io_err_watch_dispatch, > + .finalize = io_err_watch_finalize, > + }; > + GSourceFunc callback = (void *)func; > + struct io_err_watch *watch; > + gint fd; > + guint id; > + > + g_return_val_if_fail(!(events & (G_IO_IN | G_IO_OUT)), 0); > + g_return_val_if_fail(events, 0); > + g_return_val_if_fail(func, 0); > + > + fd = g_io_channel_unix_get_fd(io); > + > + watch = (void *)g_source_new(&source_funcs, > + sizeof(struct io_err_watch)); > + > + watch->io = g_io_channel_ref(io); > + watch->events = events; > + watch->tag = g_source_add_unix_fd(&watch->source, fd, events); > + > + g_source_set_name((void *)watch, "io_glib_err_watch"); > + g_source_set_callback(&watch->source, callback, user_data, notify); > + > + if (priority != G_PRIORITY_DEFAULT) > + g_source_set_priority(&watch->source, priority); > + > + id = g_source_attach(&watch->source, NULL); > + g_source_unref(&watch->source); > + > + return id; > +} > + > +guint io_glib_add_err_watch(GIOChannel *io, GIOCondition events, GIOFunc func, > + gpointer user_data) > +{ > + return io_glib_add_err_watch_full(io, G_PRIORITY_DEFAULT, events, > + func, user_data, NULL); > +} > diff --git a/src/shared/io-glib.h b/src/shared/io-glib.h > new file mode 100644 > index 000000000..1db6fd468 > --- /dev/null > +++ b/src/shared/io-glib.h > @@ -0,0 +1,20 @@ > +/* SPDX-License-Identifier: LGPL-2.1-or-later */ > +/* > + * > + * BlueZ - Bluetooth protocol stack for Linux > + * > + * Copyright (C) 2012-2014 Intel Corporation. All rights reserved. > + * > + * > + */ > + > +#include <glib.h> > + > +guint io_glib_add_err_watch(GIOChannel *io, GIOCondition events, > + GIOFunc func, gpointer user_data); > +guint io_glib_add_err_watch_full(GIOChannel *io, gint priority, > + GIOCondition events, GIOFunc func, > + gpointer user_data, > + GDestroyNotify notify); > + Hmm, I think it is probably not a good idea to start using headers like this, I'd consider just making it return 0 for non-glib. > +bool io_set_use_err_watch(struct io *io, bool err_watch); > -- > 2.48.1 > > -- Luiz Augusto von Dentz