diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 16:03:18 +0000 |
commit | 2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1 (patch) | |
tree | 465b29cb405d3af0b0ad50c78e1dccc636594fec /src/pulsecore/lock-autospawn.c | |
parent | Initial commit. (diff) | |
download | pulseaudio-2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1.tar.xz pulseaudio-2dd5bc6a074165ddfbd57c4bd52c2d2dac8f47a1.zip |
Adding upstream version 14.2.upstream/14.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pulsecore/lock-autospawn.c')
-rw-r--r-- | src/pulsecore/lock-autospawn.c | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/src/pulsecore/lock-autospawn.c b/src/pulsecore/lock-autospawn.c new file mode 100644 index 0000000..955fd23 --- /dev/null +++ b/src/pulsecore/lock-autospawn.c @@ -0,0 +1,362 @@ +/*** + This file is part of PulseAudio. + + Copyright 2008 Lennart Poettering + + 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 + +#include <errno.h> +#include <string.h> +#include <signal.h> + +#ifdef HAVE_PTHREAD +#include <pthread.h> +#endif + +#include <pulse/gccmacro.h> +#include <pulse/xmalloc.h> + +#include <pulsecore/i18n.h> +#include <pulsecore/poll.h> +#include <pulsecore/mutex.h> +#include <pulsecore/thread.h> +#include <pulsecore/core-util.h> + +#include "lock-autospawn.h" + +/* So, why do we have this complex code here with threads and pipes + * and stuff? For two reasons: POSIX file locks are per-process, not + * per-file descriptor. That means that two contexts within the same + * process that try to create the autospawn lock might end up assuming + * they both managed to lock the file. And then, POSIX locking + * operations are synchronous. If two contexts run from the same event + * loop it must be made sure that they do not block each other, but + * that the locking operation can happen asynchronously. */ + +#define AUTOSPAWN_LOCK "autospawn.lock" + +static pa_mutex *mutex; + +static unsigned n_ref = 0; +static int lock_fd = -1; +static pa_mutex *lock_fd_mutex = NULL; +static pa_thread *thread = NULL; +static int pipe_fd[2] = { -1, -1 }; + +static enum { + STATE_IDLE, + STATE_OWNING, + STATE_TAKEN, + STATE_FAILED +} state = STATE_IDLE; + +static void destroy_mutex(void) PA_GCC_DESTRUCTOR; + +static int ref(void) { + + if (n_ref > 0) { + + pa_assert(pipe_fd[0] >= 0); + pa_assert(pipe_fd[1] >= 0); + pa_assert(lock_fd_mutex); + + n_ref++; + + return 0; + } + + pa_assert(!lock_fd_mutex); + pa_assert(state == STATE_IDLE); + pa_assert(lock_fd < 0); + pa_assert(!thread); + pa_assert(pipe_fd[0] < 0); + pa_assert(pipe_fd[1] < 0); + + if (pa_pipe_cloexec(pipe_fd) < 0) + return -1; + + pa_make_fd_nonblock(pipe_fd[1]); + pa_make_fd_nonblock(pipe_fd[0]); + + lock_fd_mutex = pa_mutex_new(false, false); + + n_ref = 1; + return 0; +} + +static void unref(bool after_fork) { + + pa_assert(n_ref > 0); + pa_assert(pipe_fd[0] >= 0); + pa_assert(pipe_fd[1] >= 0); + pa_assert(lock_fd_mutex); + + n_ref--; + + if (n_ref > 0) + return; + + /* Join threads only in the process the new thread was created in + * to avoid undefined behaviour. + * POSIX.1-2008 XSH 2.9.2 Thread IDs: "applications should only assume + * that thread IDs are usable and unique within a single process." */ + if (thread) { + if (after_fork) + pa_thread_free_nojoin(thread); + else + pa_thread_free(thread); + thread = NULL; + } + + pa_mutex_lock(lock_fd_mutex); + + pa_assert(state != STATE_TAKEN); + + if (state == STATE_OWNING) { + + pa_assert(lock_fd >= 0); + + if (after_fork) + pa_close(lock_fd); + else { + char *lf; + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) + pa_log_warn(_("Cannot access autospawn lock.")); + + pa_unlock_lockfile(lf, lock_fd); + pa_xfree(lf); + } + } + + lock_fd = -1; + state = STATE_IDLE; + + pa_mutex_unlock(lock_fd_mutex); + + pa_mutex_free(lock_fd_mutex); + lock_fd_mutex = NULL; + + pa_close(pipe_fd[0]); + pa_close(pipe_fd[1]); + pipe_fd[0] = pipe_fd[1] = -1; +} + +static void ping(void) { + ssize_t s; + + pa_assert(pipe_fd[1] >= 0); + + for (;;) { + char x = 'x'; + + if ((s = pa_write(pipe_fd[1], &x, 1, NULL)) == 1) + break; + + pa_assert(s < 0); + + if (errno == EAGAIN) + break; + + pa_assert(errno == EINTR); + } +} + +static void wait_for_ping(void) { + ssize_t s; + char x; + struct pollfd pfd; + int k; + + pa_assert(pipe_fd[0] >= 0); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = pipe_fd[0]; + pfd.events = POLLIN; + + if ((k = pa_poll(&pfd, 1, -1)) != 1) { + pa_assert(k < 0); + pa_assert(errno == EINTR); + } else if ((s = pa_read(pipe_fd[0], &x, 1, NULL)) != 1) { + pa_assert(s < 0); + pa_assert(errno == EAGAIN); + } +} + +static void empty_pipe(void) { + char x[16]; + ssize_t s; + + pa_assert(pipe_fd[0] >= 0); + + if ((s = pa_read(pipe_fd[0], &x, sizeof(x), NULL)) < 1) { + pa_assert(s < 0); + pa_assert(errno == EAGAIN); + } +} + +static void thread_func(void *u) { + int fd; + char *lf; + +#ifdef HAVE_PTHREAD + sigset_t fullset; + + /* No signals in this thread please */ + sigfillset(&fullset); + pthread_sigmask(SIG_BLOCK, &fullset, NULL); +#endif + + if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) { + pa_log_warn(_("Cannot access autospawn lock.")); + goto fail; + } + + if ((fd = pa_lock_lockfile(lf)) < 0) + goto fail; + + pa_mutex_lock(lock_fd_mutex); + pa_assert(state == STATE_IDLE); + lock_fd = fd; + state = STATE_OWNING; + pa_mutex_unlock(lock_fd_mutex); + + goto finish; + +fail: + pa_mutex_lock(lock_fd_mutex); + pa_assert(state == STATE_IDLE); + state = STATE_FAILED; + pa_mutex_unlock(lock_fd_mutex); + +finish: + pa_xfree(lf); + + ping(); +} + +static int start_thread(void) { + + if (!thread) + if (!(thread = pa_thread_new("autospawn", thread_func, NULL))) + return -1; + + return 0; +} + +static void create_mutex(void) { + PA_ONCE_BEGIN { + mutex = pa_mutex_new(false, false); + } PA_ONCE_END; +} + +static void destroy_mutex(void) { + if (mutex) + pa_mutex_free(mutex); +} + +int pa_autospawn_lock_init(void) { + int ret = -1; + + create_mutex(); + pa_mutex_lock(mutex); + + if (ref() < 0) + ret = -1; + else + ret = pipe_fd[0]; + + pa_mutex_unlock(mutex); + + return ret; +} + +int pa_autospawn_lock_acquire(bool block) { + int ret = -1; + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + pa_mutex_lock(lock_fd_mutex); + + for (;;) { + + empty_pipe(); + + if (state == STATE_OWNING) { + state = STATE_TAKEN; + ret = 1; + break; + } + + if (state == STATE_FAILED) { + ret = -1; + break; + } + + if (state == STATE_IDLE) + if (start_thread() < 0) + break; + + if (!block) { + ret = 0; + break; + } + + pa_mutex_unlock(lock_fd_mutex); + pa_mutex_unlock(mutex); + + wait_for_ping(); + + pa_mutex_lock(mutex); + pa_mutex_lock(lock_fd_mutex); + } + + pa_mutex_unlock(lock_fd_mutex); + + pa_mutex_unlock(mutex); + + return ret; +} + +void pa_autospawn_lock_release(void) { + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + pa_assert(state == STATE_TAKEN); + state = STATE_OWNING; + + ping(); + + pa_mutex_unlock(mutex); +} + +void pa_autospawn_lock_done(bool after_fork) { + + create_mutex(); + pa_mutex_lock(mutex); + pa_assert(n_ref >= 1); + + unref(after_fork); + + pa_mutex_unlock(mutex); +} |