From 6378e6afd579a38968f8372d495cabf3c9495e5c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 2 Jul 2015 02:45:02 -0700 Subject: [PATCH] implement conditions and signals with kqueue now libsoundio builds on OS X and FreeBSD --- src/atomics.hpp | 22 ++++++ src/dummy.cpp | 29 ++----- src/dummy_ring_buffer.hpp | 7 +- src/os.cpp | 157 +++++++++++++++++++++++++++++++++++--- src/os.hpp | 17 ++++- src/pulseaudio.cpp | 3 +- 6 files changed, 190 insertions(+), 45 deletions(-) create mode 100644 src/atomics.hpp diff --git a/src/atomics.hpp b/src/atomics.hpp new file mode 100644 index 0000000..30338d3 --- /dev/null +++ b/src/atomics.hpp @@ -0,0 +1,22 @@ +#ifndef SOUNDIO_ATOMICS_HPP +#define SOUNDIO_ATOMICS_HPP + +#include +using std::atomic_flag; +using std::atomic_long; +using std::atomic_bool; +using std::atomic_uintptr_t; + +#if ATOMIC_LONG_LOCK_FREE != 2 +#error "require atomic_long to be lock free" +#endif + +#if ATOMIC_BOOL_LOCK_FREE != 2 +#error "require atomic_bool to be lock free" +#endif + +#if ATOMIC_POINTER_LOCK_FREE != 2 +#error "require atomic pointers to be lock free" +#endif + +#endif diff --git a/src/dummy.cpp b/src/dummy.cpp index 3a4efbd..3c16b5c 100644 --- a/src/dummy.cpp +++ b/src/dummy.cpp @@ -9,15 +9,13 @@ #include "soundio.hpp" #include "dummy_ring_buffer.hpp" #include "os.hpp" +#include "atomics.hpp" #include #include -#include -using std::atomic_flag; struct SoundIoOutputDeviceDummy { struct SoundIoOsThread *thread; - struct SoundIoOsMutex *mutex; struct SoundIoOsCond *cond; atomic_flag abort_flag; int buffer_size; @@ -45,9 +43,7 @@ static void playback_thread_run(void *arg) { double time_per_frame = 1.0 / (double)device->default_sample_rate; while (opd->abort_flag.test_and_set()) { - soundio_os_mutex_lock(opd->mutex); - soundio_os_cond_timed_wait(opd->cond, opd->mutex, opd->period); - soundio_os_mutex_unlock(opd->mutex); + soundio_os_cond_timed_wait(opd->cond, nullptr, opd->period); double now = soundio_os_get_time(); double total_time = now - start_time; @@ -104,16 +100,12 @@ static void flush_events(SoundIo *soundio) { static void wait_events(SoundIo *soundio) { SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; flush_events(soundio); - soundio_os_mutex_lock(sid->mutex); - soundio_os_cond_wait(sid->cond, sid->mutex); - soundio_os_mutex_unlock(sid->mutex); + soundio_os_cond_wait(sid->cond, nullptr); } static void wakeup(SoundIo *soundio) { SoundIoDummy *sid = (SoundIoDummy *)soundio->backend_data; - soundio_os_mutex_lock(sid->mutex); - soundio_os_cond_signal(sid->cond); - soundio_os_mutex_unlock(sid->mutex); + soundio_os_cond_signal(sid->cond, nullptr); } static void refresh_devices(SoundIo *soundio) { } @@ -125,16 +117,11 @@ static void output_device_destroy_dummy(SoundIo *soundio, if (opd->thread) { if (opd->thread) { opd->abort_flag.clear(); - soundio_os_mutex_lock(opd->mutex); - soundio_os_cond_signal(opd->cond); - soundio_os_mutex_unlock(opd->mutex); + soundio_os_cond_signal(opd->cond, nullptr); soundio_os_thread_destroy(opd->thread); opd->thread = nullptr; } } - soundio_os_mutex_destroy(opd->mutex); - opd->mutex = nullptr; - soundio_os_cond_destroy(opd->cond); opd->cond = nullptr; } @@ -150,12 +137,6 @@ static int output_device_init_dummy(SoundIo *soundio, soundio_dummy_ring_buffer_init(&opd->ring_buffer, opd->buffer_size); - opd->mutex = soundio_os_mutex_create(); - if (!opd->mutex) { - output_device_destroy_dummy(soundio, output_device); - return SoundIoErrorNoMem; - } - opd->cond = soundio_os_cond_create(); if (!opd->cond) { output_device_destroy_dummy(soundio, output_device); diff --git a/src/dummy_ring_buffer.hpp b/src/dummy_ring_buffer.hpp index e5a1819..830445c 100644 --- a/src/dummy_ring_buffer.hpp +++ b/src/dummy_ring_buffer.hpp @@ -2,12 +2,7 @@ #define SOUNDIO_DUMMY_RING_BUFFER_HPP #include "util.hpp" -#include -using std::atomic_long; - -#ifndef ATOMIC_LONG_LOCK_FREE -#error "require atomic long to be lock free" -#endif +#include "atomics.hpp" struct SoundIoDummyRingBuffer { char *address; diff --git a/src/os.cpp b/src/os.cpp index 09c584e..706479a 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -8,6 +8,7 @@ #include "os.hpp" #include "soundio.h" #include "util.hpp" +#include "atomics.hpp" #include #include @@ -18,6 +19,16 @@ #include #include +#if defined(__FreeBSD__) || defined(__MACH__) +#define SOUNDIO_OS_KQUEUE +#endif + +#ifdef SOUNDIO_OS_KQUEUE +#include +#include +#include +#endif + #ifdef __MACH__ #include #include @@ -39,13 +50,27 @@ struct SoundIoOsMutex { bool id_init; }; +#ifdef SOUNDIO_OS_KQUEUE +static int kq_id; +static atomic_uintptr_t next_notify_ident; +struct SoundIoOsCond { + int notify_ident; +}; +#else struct SoundIoOsCond { pthread_cond_t id; bool id_init; pthread_condattr_t attr; bool attr_init; + + pthread_mutex_t default_mutex_id; + bool default_mutex_init; }; +#endif + +static atomic_bool initialized = ATOMIC_VAR_INIT(false); +static pthread_mutex_t init_mutex = PTHREAD_MUTEX_INITIALIZER; double soundio_os_get_time(void) { #ifdef __MACH__ @@ -207,6 +232,9 @@ struct SoundIoOsCond * soundio_os_cond_create(void) { return NULL; } +#ifdef SOUNDIO_OS_KQUEUE + cond->notify_ident = next_notify_ident.fetch_add(1); +#else if (pthread_condattr_init(&cond->attr)) { soundio_os_cond_destroy(cond); return NULL; @@ -224,6 +252,13 @@ struct SoundIoOsCond * soundio_os_cond_create(void) { } cond->id_init = true; + if ((err = pthread_mutex_init(&cond->default_mutex_id, NULL))) { + soundio_os_cond_destroy(cond); + return NULL; + } + cond->default_mutex_init = true; +#endif + return cond; } @@ -231,6 +266,8 @@ void soundio_os_cond_destroy(struct SoundIoOsCond *cond) { if (!cond) return; +#ifdef SOUNDIO_OS_KQUEUE +#else if (cond->id_init) { assert_no_err(pthread_cond_destroy(&cond->id)); } @@ -238,37 +275,139 @@ void soundio_os_cond_destroy(struct SoundIoOsCond *cond) { if (cond->attr_init) { assert_no_err(pthread_condattr_destroy(&cond->attr)); } + if (cond->default_mutex_init) { + assert_no_err(pthread_mutex_destroy(&cond->default_mutex_id)); + } +#endif destroy(cond); } -void soundio_os_cond_signal(struct SoundIoOsCond *cond) { - assert_no_err(pthread_cond_signal(&cond->id)); -} +void soundio_os_cond_signal(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *locked_mutex) +{ +#ifdef SOUNDIO_OS_KQUEUE + struct kevent kev; + struct timespec timeout = { 0, 0 }; -void soundio_os_cond_broadcast(struct SoundIoOsCond *cond) { - assert_no_err(pthread_cond_broadcast(&cond->id)); + memset(&kev, 0, sizeof(kev)); + kev.ident = cond->notify_ident; + kev.filter = EVFILT_USER; + kev.fflags = NOTE_TRIGGER; + + if (kevent(kq_id, &kev, 1, NULL, 0, &timeout) == -1) { + if (errno == EINTR) + return; + soundio_panic("kevent signal error: %s", strerror(errno)); + } +#else + if (locked_mutex) { + assert_no_err(pthread_cond_signal(&cond->id)); + } else { + assert_no_err(pthread_mutex_lock(&cond->default_mutex_id)); + assert_no_err(pthread_cond_signal(&cond->id)); + assert_no_err(pthread_mutex_unlock(&cond->default_mutex_id)); + } +#endif } void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, - struct SoundIoOsMutex *mutex, double seconds) + struct SoundIoOsMutex *locked_mutex, double seconds) { +#ifdef SOUNDIO_OS_KQUEUE + struct kevent kev; + struct kevent out_kev; + + memset(&kev, 0, sizeof(kev)); + kev.ident = cond->notify_ident; + kev.filter = EVFILT_USER; + kev.flags = EV_ADD | EV_CLEAR; + + // this time is relative + struct timespec timeout; + timeout.tv_sec = 0; + timeout.tv_nsec = (seconds * 1000000000L); + + if (kevent(kq_id, &kev, 1, &out_kev, 1, &timeout) == -1) { + if (errno == EINTR) + return; + soundio_panic("kevent wait error: %s", strerror(errno)); + } +#else + pthread_mutex_t *target_mutex; + if (locked_mutex) { + target_mutex = &locked_mutex->id; + } else { + target_mutex = &cond->default_mutex_id; + assert_no_err(pthread_mutex_lock(&cond->default_mutex_id)); + } + // this time is absolute struct timespec tms; clock_gettime(CLOCK_MONOTONIC, &tms); tms.tv_nsec += (seconds * 1000000000L); int err; - if ((err = pthread_cond_timedwait(&cond->id, &mutex->id, &tms))) { + if ((err = pthread_cond_timedwait(&cond->id, target_mutex, &tms))) { assert(err != EPERM); assert(err != EINVAL); } + if (!locked_mutex) + assert_no_err(pthread_mutex_unlock(&cond->default_mutex_id)); +#endif } void soundio_os_cond_wait(struct SoundIoOsCond *cond, - struct SoundIoOsMutex *mutex) + struct SoundIoOsMutex *locked_mutex) { +#ifdef SOUNDIO_OS_KQUEUE + struct kevent kev; + struct kevent out_kev; + + memset(&kev, 0, sizeof(kev)); + kev.ident = cond->notify_ident; + kev.filter = EVFILT_USER; + kev.flags = EV_ADD | EV_CLEAR; + + if (kevent(kq_id, &kev, 1, &out_kev, 1, NULL) == -1) { + if (errno == EINTR) + return; + soundio_panic("kevent wait error: %s", strerror(errno)); + } +#else + pthread_mutex_t *target_mutex; + if (locked_mutex) { + target_mutex = &locked_mutex->id; + } else { + target_mutex = &cond->default_mutex_id; + assert_no_err(pthread_mutex_lock(&cond->default_mutex_id)); + } int err; - if ((err = pthread_cond_wait(&cond->id, &mutex->id))) { + if ((err = pthread_cond_wait(&cond->id, target_mutex))) { assert(err != EPERM); assert(err != EINVAL); } + if (!locked_mutex) + assert_no_err(pthread_mutex_unlock(&cond->default_mutex_id)); +#endif +} + +static void internal_init(void) { +#ifdef SOUNDIO_OS_KQUEUE + kq_id = kqueue(); + if (kq_id == -1) + soundio_panic("unable to create kqueue: %s", strerror(errno)); + next_notify_ident.store(1); +#endif +} + +void soundio_os_init(void) { + if (initialized.load()) + return; + assert_no_err(pthread_mutex_lock(&init_mutex)); + if (initialized.load()) { + assert_no_err(pthread_mutex_unlock(&init_mutex)); + return; + } + initialized.store(true); + internal_init(); + assert_no_err(pthread_mutex_unlock(&init_mutex)); } diff --git a/src/os.hpp b/src/os.hpp index b221e11..5a1a0d5 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -11,6 +11,10 @@ #include +// safe to call from any thread(s) multiple times, but +// must be called at least once before calling any other os functions +void soundio_os_init(void); + double soundio_os_get_time(void); struct SoundIoOsThread; @@ -30,12 +34,17 @@ void soundio_os_mutex_unlock(struct SoundIoOsMutex *mutex); struct SoundIoOsCond; struct SoundIoOsCond * soundio_os_cond_create(void); void soundio_os_cond_destroy(struct SoundIoOsCond *cond); -void soundio_os_cond_signal(struct SoundIoOsCond *cond); -void soundio_os_cond_broadcast(struct SoundIoOsCond *cond); +// locked_mutex is optional. On systems that use mutexes for conditions, if you +// pass NULL, a mutex will be created and locked/unlocked for you. On systems +// that do not use mutexes for conditions, no mutex handling is necessary. If +// you already have a locked mutex available, pass it; this will be better on +// systems that use mutexes for conditions. +void soundio_os_cond_signal(struct SoundIoOsCond *cond, + struct SoundIoOsMutex *locked_mutex); void soundio_os_cond_timed_wait(struct SoundIoOsCond *cond, - struct SoundIoOsMutex *mutex, double seconds); + struct SoundIoOsMutex *locked_mutex, double seconds); void soundio_os_cond_wait(struct SoundIoOsCond *cond, - struct SoundIoOsMutex *mutex); + struct SoundIoOsMutex *locked_mutex); #endif diff --git a/src/pulseaudio.cpp b/src/pulseaudio.cpp index 43238e7..b34f2e8 100644 --- a/src/pulseaudio.cpp +++ b/src/pulseaudio.cpp @@ -1,12 +1,11 @@ #include "pulseaudio.hpp" #include "soundio.hpp" +#include "atomics.hpp" #include #include #include -#include -using std::atomic_bool; struct SoundIoOutputDevicePulseAudio { pa_stream *stream;