mirror of
https://gitlab.freedesktop.org/pulseaudio/pulseaudio.git
synced 2025-10-31 22:25:33 -04:00
Don't call pthread_join() to join a thread from a different process than the thread was created in. Doing so can lead to undefined behaviour. On OpenBSD, the symptom was a pulseaudio process with a single thread waiting forever for other threads to join. Since that process also held the autospawn lock, starting new pulseaudio processes with --start kept failing. The problem was analyzed with help from Philip Guenther. This patch adds a pa_thread_free_nojoin() function which can be used to free resources for a thread without a join, as suggested by Tanu Kaskinen. See https://bugs.freedesktop.org/show_bug.cgi?id=71738
364 lines
7.4 KiB
C
364 lines
7.4 KiB
C
/***
|
|
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, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
|
USA.
|
|
***/
|
|
|
|
#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);
|
|
}
|