diff --git a/src/map-file b/src/map-file index aaa1a424f..b0cd1bfc4 100644 --- a/src/map-file +++ b/src/map-file @@ -360,6 +360,7 @@ pa_threaded_mainloop_get_retval; pa_threaded_mainloop_in_thread; pa_threaded_mainloop_lock; pa_threaded_mainloop_new; +pa_threaded_mainloop_once_unlocked; pa_threaded_mainloop_set_name; pa_threaded_mainloop_signal; pa_threaded_mainloop_start; diff --git a/src/pulse/thread-mainloop.c b/src/pulse/thread-mainloop.c index 0205a4a70..495cc1eb6 100644 --- a/src/pulse/thread-mainloop.c +++ b/src/pulse/thread-mainloop.c @@ -44,6 +44,7 @@ struct pa_threaded_mainloop { pa_mainloop *real_mainloop; volatile int n_waiting, n_waiting_for_accept; + pa_atomic_t in_once_unlocked; pa_thread* thread; pa_mutex* mutex; @@ -174,8 +175,8 @@ void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { pa_assert(m); - /* Make sure that this function is not called from the helper thread */ - pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + /* Make sure that this function is not called from the helper thread, unless it is unlocked */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)); pa_mutex_lock(m->mutex); } @@ -183,8 +184,8 @@ void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) { pa_assert(m); - /* Make sure that this function is not called from the helper thread */ - pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m)); + /* Make sure that this function is not called from the helper thread, unless it is unlocked */ + pa_assert(!m->thread || !pa_thread_is_running(m->thread) || !in_worker(m) || pa_atomic_load(&m->in_once_unlocked)); pa_mutex_unlock(m->mutex); } @@ -258,3 +259,43 @@ void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name) { if (m->thread) pa_thread_set_name(m->thread, m->name); } + +typedef struct { + pa_threaded_mainloop *mainloop; + void (*callback)(pa_threaded_mainloop *m, void *userdata); + void *userdata; +} once_unlocked_data; + +static void once_unlocked_cb(pa_mainloop_api *api, void *userdata) { + once_unlocked_data *data = userdata; + + pa_assert(userdata); + + pa_atomic_store(&data->mainloop->in_once_unlocked, 1); + pa_mutex_unlock(data->mainloop->mutex); + + data->callback(data->mainloop, data->userdata); + + pa_mutex_lock(data->mainloop->mutex); + pa_atomic_store(&data->mainloop->in_once_unlocked, 0); +} + +void pa_threaded_mainloop_once_unlocked(pa_threaded_mainloop *m, void (*callback)(pa_threaded_mainloop *m, void *userdata), + void *userdata) { + pa_mainloop_api *api; + once_unlocked_data *data; + + pa_assert(m); + pa_assert(callback); + /* Make sure that this function is not called from the helper thread */ + pa_assert((m->thread && !pa_thread_is_running(m->thread)) || !in_worker(m)); + + api = pa_mainloop_get_api(m->real_mainloop); + data = pa_xnew0(once_unlocked_data, 1); + + data->mainloop = m; + data->callback = callback; + data->userdata = userdata; + + pa_mainloop_api_once(api, once_unlocked_cb, data); +} diff --git a/src/pulse/thread-mainloop.h b/src/pulse/thread-mainloop.h index 4dbc2d206..26fa4edbb 100644 --- a/src/pulse/thread-mainloop.h +++ b/src/pulse/thread-mainloop.h @@ -312,6 +312,16 @@ int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m); /** Sets the name of the thread. \since 5.0 */ void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name); +/** Runs the given callback in the mainloop thread without the lock held. The + * caller is responsible for ensuring that PulseAudio data structures are only + * accessed in a thread-safe way (that is, APIs that take pa_context and + * pa_stream are not thread-safe, and should not accessed without some + * synchronisation). This is the only situation in which + * pa_threaded_mainloop_lock() and pa_threaded_mainloop_unlock() may be used + * in the mainloop thread context. \since 13.0 */ +void pa_threaded_mainloop_once_unlocked(pa_threaded_mainloop *m, void (*callback)(pa_threaded_mainloop *m, void *userdata), + void *userdata); + PA_C_DECL_END #endif diff --git a/src/tests/thread-mainloop-test.c b/src/tests/thread-mainloop-test.c index af366a56c..5f6952cde 100644 --- a/src/tests/thread-mainloop-test.c +++ b/src/tests/thread-mainloop-test.c @@ -30,8 +30,9 @@ #include #include -#include #include +#include +#include static void tcb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, void *userdata) { pa_assert_se(pa_threaded_mainloop_in_thread(userdata)); @@ -40,6 +41,12 @@ static void tcb(pa_mainloop_api *a, pa_time_event *e, const struct timeval *tv, fprintf(stderr, "TIME EVENT END\n"); } +static void ocb(pa_threaded_mainloop *m, void *userdata) { + pa_threaded_mainloop_lock(m); + pa_threaded_mainloop_signal(m, 0); + pa_threaded_mainloop_unlock(m); +} + START_TEST (thread_mainloop_test) { pa_mainloop_api *a; pa_threaded_mainloop *m; @@ -69,6 +76,17 @@ START_TEST (thread_mainloop_test) { fprintf(stderr, "waiting 5s (sleep)\n"); pa_msleep(5000); + /* Test pa_threaded_mainloop_once_unlocked() */ + pa_threaded_mainloop_lock(m); + + fprintf(stderr, "scheduling unlocked callback\n"); + pa_threaded_mainloop_once_unlocked(m, ocb, NULL); + + pa_threaded_mainloop_wait(m); + fprintf(stderr, "got unlocked callback\n"); + + pa_threaded_mainloop_unlock(m); + pa_threaded_mainloop_stop(m); pa_threaded_mainloop_free(m);