From 5f4b422ab1bfec0b82c948bf0a3ff623424b88aa Mon Sep 17 00:00:00 2001 From: Wim Taymans Date: Tue, 10 Mar 2026 16:46:29 +0100 Subject: [PATCH] loop: improve cancellation handling Now that the loop_leave function will assert when the unlock fails we need to be extra careful with the cancellable loop. If it cancels inside the poll or one of the before/after callbacks we need to make sure that we lock the loop correctly again or we will create an assert later on. Do this by pushing the cleanup earlier and then record all the things we managed to do before we get canceled. If we ever get canceled and the lock was unlocked but not locked again, fix this up. Fix fixes issues when using the JACK API causing assertions when the data loop is stopped/cancelled. --- spa/plugins/support/loop.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/spa/plugins/support/loop.c b/spa/plugins/support/loop.c index 88364b7b3..1ec2b21c2 100644 --- a/spa/plugins/support/loop.c +++ b/spa/plugins/support/loop.c @@ -727,13 +727,20 @@ static int loop_accept(void *object) } struct cancellation_handler_data { + struct impl *impl; struct spa_poll_event *ep; int ep_count; + int unlocked; + int locked; }; static void cancellation_handler(void *closure) { const struct cancellation_handler_data *data = closure; + struct impl *impl = data->impl; + + if (data->unlocked && !data->locked) + spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); for (int i = 0; i < data->ep_count; i++) { struct spa_source *s = data->ep[i].data; @@ -750,22 +757,24 @@ static int loop_iterate_cancel(void *object, int timeout) struct spa_poll_event ep[MAX_EP], *e; int i, nfds; uint32_t remove_count; + struct cancellation_handler_data cdata = { impl, ep, 0, 0, 0 }; spa_return_val_if_fail(impl->enter_count > 0, -EPERM); + pthread_cleanup_push(cancellation_handler, &cdata); + remove_count = impl->remove_count; spa_loop_control_hook_before(&impl->hooks_list); - spa_assert_se(pthread_mutex_unlock(&impl->lock) == 0); + spa_assert_se((cdata.unlocked = (pthread_mutex_unlock(&impl->lock) == 0))); nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout); - spa_assert_se(pthread_mutex_lock(&impl->lock) == 0); + spa_assert_se((cdata.locked = (pthread_mutex_lock(&impl->lock) == 0))); spa_loop_control_hook_after(&impl->hooks_list); if (remove_count != impl->remove_count) nfds = 0; - struct cancellation_handler_data cdata = { ep, nfds }; - pthread_cleanup_push(cancellation_handler, &cdata); + cdata.ep_count = nfds; /* first we set all the rmasks, then call the callbacks. The reason is that * some callback might also want to look at other sources it manages and