loop: make safe version of befor and after signal

Use a safer version of the before and after hooks. First call
all before hooks and save them in reverse order in a save list.
Then call the after event for the ones remaining in the save list
and move them back to the hook list.

This makes it possible to remove the hooks from one the callbacks or
even from other threads with the right locks. Found as a solution to
the following problem as observed in vlc:

main thread                            thread_loop

pw_thread_loop_lock()            before hook: lock suspend thread
pw_context_destroy()
  - removes before hook to flush clients
pw_thread_loop_unlock()
                                 before hook: lock acquired, resume
				 before hook: flush client hook executed
				   *crash*

pw_thread_loop_stop()
pw_thread_loop_destroy()

Any of the safer cursor methods (like spa_hook_list_call()) would also
work but are more expensive and don't reverse the before/after
order.
This commit is contained in:
Wim Taymans 2020-09-15 20:13:32 +02:00
parent b5517cc2fe
commit 05ae8a24de

View file

@ -278,13 +278,24 @@ static int loop_iterate(void *object, int timeout)
struct impl *impl = object;
struct spa_loop *loop = &impl->loop;
struct spa_poll_event ep[32];
struct spa_list save;
struct spa_hook *hook;
int i, nfds;
spa_loop_control_hook_before(&impl->hooks_list);
spa_list_init(&save);
spa_list_consume(hook, &impl->hooks_list.list, link) {
spa_list_remove(&hook->link);
spa_list_prepend(&save, &hook->link);
spa_callbacks_call(&hook->cb, struct spa_loop_control_hooks, before, 0);
}
nfds = spa_system_pollfd_wait(impl->system, impl->poll_fd, ep, SPA_N_ELEMENTS(ep), timeout);
spa_loop_control_hook_after(&impl->hooks_list);
spa_list_consume(hook, &save, link) {
spa_list_remove(&hook->link);
spa_list_append(&impl->hooks_list.list, &hook->link);
spa_callbacks_call(&hook->cb, struct spa_loop_control_hooks, after, 0);
}
if (SPA_UNLIKELY(nfds < 0))
return nfds;