implement conditions and signals with kqueue

now libsoundio builds on OS X and FreeBSD
This commit is contained in:
Andrew Kelley 2015-07-02 02:45:02 -07:00
parent 24557e0ee3
commit 6378e6afd5
6 changed files with 190 additions and 45 deletions

22
src/atomics.hpp Normal file
View file

@ -0,0 +1,22 @@
#ifndef SOUNDIO_ATOMICS_HPP
#define SOUNDIO_ATOMICS_HPP
#include <atomic>
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

View file

@ -9,15 +9,13 @@
#include "soundio.hpp"
#include "dummy_ring_buffer.hpp"
#include "os.hpp"
#include "atomics.hpp"
#include <stdio.h>
#include <string.h>
#include <atomic>
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);

View file

@ -2,12 +2,7 @@
#define SOUNDIO_DUMMY_RING_BUFFER_HPP
#include "util.hpp"
#include <atomic>
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;

View file

@ -8,6 +8,7 @@
#include "os.hpp"
#include "soundio.h"
#include "util.hpp"
#include "atomics.hpp"
#include <stdlib.h>
#include <time.h>
@ -18,6 +19,16 @@
#include <errno.h>
#include <stdio.h>
#if defined(__FreeBSD__) || defined(__MACH__)
#define SOUNDIO_OS_KQUEUE
#endif
#ifdef SOUNDIO_OS_KQUEUE
#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
#endif
#ifdef __MACH__
#include <mach/clock.h>
#include <mach/mach.h>
@ -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));
}

View file

@ -11,6 +11,10 @@
#include <stdbool.h>
// 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

View file

@ -1,12 +1,11 @@
#include "pulseaudio.hpp"
#include "soundio.hpp"
#include "atomics.hpp"
#include <string.h>
#include <math.h>
#include <pulse/pulseaudio.h>
#include <atomic>
using std::atomic_bool;
struct SoundIoOutputDevicePulseAudio {
pa_stream *stream;