| /*** | |
| This file is part of PulseAudio. | |
| Copyright 2006 Lennart Poettering | |
| Copyright 2006 Pierre Ossman <ossman@cendio.se> for Cendio AB | |
| PulseAudio is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU Lesser General Public License as published | |
| by the Free Software Foundation; either version 2.1 of the License, | |
| or (at your option) any later version. | |
| PulseAudio is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU Lesser General Public License | |
| along with PulseAudio; if not, see <http://www.gnu.org/licenses/>. | |
| ***/ | |
| #ifdef HAVE_CONFIG_H | |
| #include <config.h> | |
| #endif | |
| #ifndef OS_IS_WIN32 | |
| #include <pthread.h> | |
| #endif | |
| #include <signal.h> | |
| #include <stdio.h> | |
| #include <pulse/xmalloc.h> | |
| #include <pulse/mainloop.h> | |
| #include <pulsecore/i18n.h> | |
| #include <pulsecore/log.h> | |
| #include <pulsecore/thread.h> | |
| #include <pulsecore/mutex.h> | |
| #include <pulsecore/macro.h> | |
| #include <pulsecore/poll.h> | |
| #include "thread-mainloop.h" | |
| 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; | |
| pa_cond* cond, *accept_cond; | |
| char *name; | |
| }; | |
| static inline int in_worker(pa_threaded_mainloop *m) { | |
| return pa_thread_self() == m->thread; | |
| } | |
| static int poll_func(struct pollfd *ufds, unsigned long nfds, int timeout, void *userdata) { | |
| pa_mutex *mutex = userdata; | |
| int r; | |
| pa_assert(mutex); | |
| /* Before entering poll() we unlock the mutex, so that | |
| * avahi_simple_poll_quit() can succeed from another thread. */ | |
| pa_mutex_unlock(mutex); | |
| r = pa_poll(ufds, nfds, timeout); | |
| pa_mutex_lock(mutex); | |
| return r; | |
| } | |
| static void thread(void *userdata) { | |
| pa_threaded_mainloop *m = userdata; | |
| #ifndef OS_IS_WIN32 | |
| sigset_t mask; | |
| sigset_t prev_mask; | |
| struct sigaction sa; | |
| sigfillset(&mask); | |
| /* If SIGSYS is currently unblocked and trapped then keep it unblocked. */ | |
| if (!pthread_sigmask(SIG_SETMASK, NULL, &prev_mask) && | |
| !sigismember(&prev_mask, SIGSYS) && | |
| !sigaction(SIGSYS, NULL, &sa) | |
| && sa.sa_handler != SIG_DFL) { | |
| sigdelset(&mask, SIGSYS); | |
| } | |
| /* Make sure that signals are delivered to the main thread. | |
| * Use SIG_SETMASK because SIG_BLOCK does an union with current set.*/ | |
| pthread_sigmask(SIG_SETMASK, &mask, NULL); | |
| #endif | |
| pa_mutex_lock(m->mutex); | |
| (void) pa_mainloop_run(m->real_mainloop, NULL); | |
| pa_mutex_unlock(m->mutex); | |
| } | |
| pa_threaded_mainloop *pa_threaded_mainloop_new(void) { | |
| pa_threaded_mainloop *m; | |
| pa_init_i18n(); | |
| m = pa_xnew0(pa_threaded_mainloop, 1); | |
| if (!(m->real_mainloop = pa_mainloop_new())) { | |
| pa_xfree(m); | |
| return NULL; | |
| } | |
| m->mutex = pa_mutex_new(true, true); | |
| m->cond = pa_cond_new(); | |
| m->accept_cond = pa_cond_new(); | |
| pa_mainloop_set_poll_func(m->real_mainloop, poll_func, m->mutex); | |
| return m; | |
| } | |
| void pa_threaded_mainloop_free(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)); | |
| pa_threaded_mainloop_stop(m); | |
| if (m->thread) | |
| pa_thread_free(m->thread); | |
| pa_mainloop_free(m->real_mainloop); | |
| pa_mutex_free(m->mutex); | |
| pa_cond_free(m->cond); | |
| pa_cond_free(m->accept_cond); | |
| pa_xfree(m->name); | |
| pa_xfree(m); | |
| } | |
| int pa_threaded_mainloop_start(pa_threaded_mainloop *m) { | |
| pa_assert(m); | |
| pa_assert(!m->thread || !pa_thread_is_running(m->thread)); | |
| if (!(m->thread = pa_thread_new(m->name ? m->name : "threaded-ml", thread, m))) | |
| return -1; | |
| return 0; | |
| } | |
| void pa_threaded_mainloop_stop(pa_threaded_mainloop *m) { | |
| pa_assert(m); | |
| if (!m->thread || !pa_thread_is_running(m->thread)) | |
| return; | |
| /* Make sure that this function is not called from the helper thread */ | |
| pa_assert(!in_worker(m)); | |
| pa_mutex_lock(m->mutex); | |
| pa_mainloop_quit(m->real_mainloop, 0); | |
| pa_mutex_unlock(m->mutex); | |
| pa_thread_join(m->thread); | |
| } | |
| void pa_threaded_mainloop_lock(pa_threaded_mainloop *m) { | |
| pa_assert(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); | |
| } | |
| void pa_threaded_mainloop_unlock(pa_threaded_mainloop *m) { | |
| pa_assert(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); | |
| } | |
| /* Called with the lock taken */ | |
| void pa_threaded_mainloop_signal(pa_threaded_mainloop *m, int wait_for_accept) { | |
| pa_assert(m); | |
| pa_cond_signal(m->cond, 1); | |
| if (wait_for_accept) { | |
| m->n_waiting_for_accept ++; | |
| while (m->n_waiting_for_accept > 0) | |
| pa_cond_wait(m->accept_cond, m->mutex); | |
| } | |
| } | |
| /* Called with the lock taken */ | |
| void pa_threaded_mainloop_wait(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)); | |
| m->n_waiting ++; | |
| pa_cond_wait(m->cond, m->mutex); | |
| pa_assert(m->n_waiting > 0); | |
| m->n_waiting --; | |
| } | |
| /* Called with the lock taken */ | |
| void pa_threaded_mainloop_accept(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)); | |
| pa_assert(m->n_waiting_for_accept > 0); | |
| m->n_waiting_for_accept --; | |
| pa_cond_signal(m->accept_cond, 0); | |
| } | |
| int pa_threaded_mainloop_get_retval(const pa_threaded_mainloop *m) { | |
| pa_assert(m); | |
| return pa_mainloop_get_retval(m->real_mainloop); | |
| } | |
| pa_mainloop_api* pa_threaded_mainloop_get_api(pa_threaded_mainloop*m) { | |
| pa_assert(m); | |
| return pa_mainloop_get_api(m->real_mainloop); | |
| } | |
| int pa_threaded_mainloop_in_thread(pa_threaded_mainloop *m) { | |
| pa_assert(m); | |
| return m->thread && pa_thread_self() == m->thread; | |
| } | |
| void pa_threaded_mainloop_set_name(pa_threaded_mainloop *m, const char *name) { | |
| pa_assert(m); | |
| pa_assert(name); | |
| m->name = pa_xstrdup(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); | |
| pa_xfree(data); | |
| } | |
| 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); | |
| } |