diff options
Diffstat (limited to 'src/pulse/lock-autospawn.c')
| -rw-r--r-- | src/pulse/lock-autospawn.c | 330 | 
1 files changed, 330 insertions, 0 deletions
diff --git a/src/pulse/lock-autospawn.c b/src/pulse/lock-autospawn.c new file mode 100644 index 00000000..d36b669e --- /dev/null +++ b/src/pulse/lock-autospawn.c @@ -0,0 +1,330 @@ +/*** +  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 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 <fcntl.h> +#include <errno.h> +#include <string.h> +#include <sys/poll.h> +#include <signal.h> +#include <pthread.h> + +#include <pulse/i18n.h> +#include <pulse/xmalloc.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_bool_t taken = FALSE; +static pa_thread *thread; +static int pipe_fd[2] = { -1, -1 }; + +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); + +        n_ref++; + +        return 0; +    } + +    pa_assert(lock_fd < 0); +    pa_assert(!lock_fd_mutex); +    pa_assert(!taken); +    pa_assert(!thread); +    pa_assert(pipe_fd[0] < 0); +    pa_assert(pipe_fd[1] < 0); + +    if (pipe(pipe_fd) < 0) +        return -1; + +    lock_fd_mutex = pa_mutex_new(FALSE, FALSE); + +    pa_make_fd_cloexec(pipe_fd[0]); +    pa_make_fd_cloexec(pipe_fd[1]); + +    pa_make_fd_nonblock(pipe_fd[1]); +    pa_make_fd_nonblock(pipe_fd[0]); + +    n_ref = 1; +    return 0; +} + +static void unref(pa_bool_t 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; + +    pa_assert(!taken); + +    if (thread) { +        pa_thread_free(thread); +        thread = NULL; +    } + +    pa_mutex_lock(lock_fd_mutex); +    if (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; +        } +    } +    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 = write(pipe_fd[1], &x, 1)) == 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 = poll(&pfd, 1, -1)) != 1) { +        pa_assert(k < 0); +        pa_assert(errno == EINTR); +    } else if ((s = read(pipe_fd[0], &x, 1)) != 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 = read(pipe_fd[0], &x, sizeof(x))) < 1) { +        pa_assert(s < 0); +        pa_assert(errno == EAGAIN); +    } +} + +static void thread_func(void *u) { +    int fd; +    char *lf; +    sigset_t fullset; + +    /* No signals in this thread please */ +    sigfillset(&fullset); +    pthread_sigmask(SIG_BLOCK, &fullset, NULL); + +    if (!(lf = pa_runtime_path(AUTOSPAWN_LOCK))) { +        pa_log_warn(_("Cannot access autospawn lock.")); +        goto finish; +    } + +    if ((fd = pa_lock_lockfile(lf)) < 0) +        goto finish; + +    pa_mutex_lock(lock_fd_mutex); +    pa_assert(lock_fd < 0); +    lock_fd = fd; +    pa_mutex_unlock(lock_fd_mutex); + +finish: +    pa_xfree(lf); + +    ping(); +} + +static int start_thread(void) { + +    if (!thread) +        if (!(thread = pa_thread_new(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(pa_bool_t 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 (lock_fd >= 0 && !taken) { +            taken = TRUE; +            ret = 1; +            break; +        } + +        if (lock_fd < 0) +            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(taken); +    taken = FALSE; + +    ping(); + +    pa_mutex_unlock(mutex); +} + +void pa_autospawn_lock_done(pa_bool_t after_fork) { + +    create_mutex(); +    pa_mutex_lock(mutex); +    pa_assert(n_ref >= 1); + +    unref(after_fork); + +    pa_mutex_unlock(mutex); +}  | 
