osdep: add POSIX semaphore emulation for OSX

OSX is POSIX conformant, but it's a sad joke: it provides the
<semaphore.h> prototype as the standard demands, but they're empty
wrappers, and all functions just return ENOSYS.

Emulate them similar to how osdep/io.h emulate filesystem functions on
Windows. By including the header, working sem_* functions become
available.

To make it async-signal safe, use a pipe for wakeup (write() is AS-safe,
but mutexes can't be). Actually I'm not sure anymore if we really need
AS-safety, but for now the emulation can do it.

On Linux, the system provides a far more efficient and robust
implementation. We definitely want to avoid using the emulation if
possible, so this code is active on OSX only. For convenience we always
build the source file though, even if the implementation is disabled and
no actual code is generated.

(Linux provides working semaphores, but is formally not POSIX
conformant. On OSX it's the opposite. Is POSIX a complete joke?)
This commit is contained in:
wm4 2014-09-10 03:09:41 +02:00
parent ae63702a2c
commit 564b957cc3
4 changed files with 181 additions and 1 deletions

View File

@ -188,6 +188,7 @@ SOURCES = audio/audio.c \
options/path.c \
osdep/io.c \
osdep/numcores.c \
osdep/semaphore_osx.c \
osdep/terminal-unix.c \
osdep/timer.c \
osdep/timer-linux.c \

58
osdep/semaphore.h Normal file
View File

@ -0,0 +1,58 @@
#ifndef MP_SEMAPHORE_H_
#define MP_SEMAPHORE_H_
#include <semaphore.h>
// OSX provides non-working empty stubs, so we emulate them.
// This should be AS-safe, but cancellation issues were ignored.
// sem_getvalue() is not provided.
// sem_post() won't always correctly return an error on overflow.
// Process-shared semantics are not provided.
#ifdef __APPLE__
#define MP_SEMAPHORE_EMULATION
#include <pthread.h>
#define MP_SEM_VALUE_MAX 4096
typedef struct {
int wakeup_pipe[2];
pthread_mutex_t lock;
// protected by lock
unsigned int count;
} mp_sem_t;
int mp_sem_init(mp_sem_t *sem, int pshared, unsigned int value);
int mp_sem_wait(mp_sem_t *sem);
int mp_sem_trywait(mp_sem_t *sem);
int mp_sem_timedwait(mp_sem_t *sem, const struct timespec *abs_timeout);
int mp_sem_post(mp_sem_t *sem);
int mp_sem_destroy(mp_sem_t *sem);
#undef sem_init
#undef sem_wait
#undef sem_trywait
#undef sem_timedwait
#undef sem_post
#undef sem_getvalue
#undef sem_destroy
#undef sem_getvalue
#undef sem_t
#undef SEM_VALUE_MAX
#define sem_init mp_sem_init
#define sem_wait mp_sem_wait
#define sem_trywait mp_sem_trywait
#define sem_timedwait mp_sem_timedwait
#define sem_post mp_sem_post
#define sem_destroy mp_sem_destroy
#define sem_t mp_sem_t
#define SEM_VALUE_MAX MP_SEM_VALUE_MAX
#define sem_getvalue (void)
#endif
#endif

121
osdep/semaphore_osx.c Normal file
View File

@ -0,0 +1,121 @@
/* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <unistd.h>
#include <poll.h>
#include <limits.h>
#include <sys/time.h>
#include <errno.h>
#include "osdep/io.h"
#include "semaphore.h"
#ifdef MP_SEMAPHORE_EMULATION
int mp_sem_init(mp_sem_t *sem, int pshared, unsigned int value)
{
if (pshared) {
errno = ENOSYS;
return -1;
}
if (value > INT_MAX) {
errno = EINVAL;
return -1;
}
if (mp_make_wakeup_pipe(sem->wakeup_pipe) < 0)
return -1;
sem->count = 0;
pthread_mutex_init(&sem->lock, NULL);
return 0;
}
int mp_sem_wait(mp_sem_t *sem)
{
return mp_sem_timedwait(sem, NULL);
}
int mp_sem_trywait(mp_sem_t *sem)
{
int r = -1;
pthread_mutex_lock(&sem->lock);
if (sem->count == 0) {
char buf[1024];
ssize_t s = read(sem->wakeup_pipe[0], buf, sizeof(buf));
if (s > 0 && s <= INT_MAX - sem->count) // can't handle overflows correctly
sem->count += s;
}
if (sem->count > 0) {
sem->count -= 1;
r = 0;
}
pthread_mutex_unlock(&sem->lock);
if (r < 0)
errno = EAGAIN;
return r;
}
int mp_sem_timedwait(mp_sem_t *sem, const struct timespec *abs_timeout)
{
while (1) {
if (!mp_sem_trywait(sem))
return 0;
int timeout_ms = -1;
if (abs_timeout) {
timeout_ms = 0;
// OSX does not provide clock_gettime() either.
struct timeval tv;
gettimeofday(&tv, NULL);
if (abs_timeout->tv_sec >= tv.tv_sec) {
long long msec = (abs_timeout->tv_sec - tv.tv_sec) * 1000LL +
abs_timeout->tv_nsec / 1000LL / 1000LL - tv.tv_usec / 1000LL;
if (msec > INT_MAX)
msec = INT_MAX;
if (msec < 0)
msec = 0;
timeout_ms = msec;
}
}
struct pollfd fd = {.fd = sem->wakeup_pipe[0], .events = POLLIN};
int r = poll(&fd, 1, timeout_ms);
if (r < 0)
return -1;
if (r == 0) {
errno = ETIMEDOUT;
return -1;
}
}
}
int mp_sem_post(mp_sem_t *sem)
{
if (write(sem->wakeup_pipe[1], &(char){0}, 1) == 1)
return 0;
// Actually we can't handle overflow fully correctly, because we can't
// check sem->count atomically, while still being AS-safe.
errno = EOVERFLOW;
return -1;
}
int mp_sem_destroy(mp_sem_t *sem)
{
close(sem->wakeup_pipe[0]);
close(sem->wakeup_pipe[1]);
pthread_mutex_destroy(&sem->lock);
return 0;
}
#endif

View File

@ -382,8 +382,8 @@ def build(ctx):
( "osdep/ar/HIDRemote.m", "cocoa" ),
( "osdep/macosx_application.m", "cocoa-application" ),
( "osdep/macosx_events.m", "cocoa" ),
( "osdep/semaphore_osx.c" ),
( "osdep/path-macosx.m", "cocoa" ),
( "osdep/path-win.c", "os-win32" ),
( "osdep/path-win.c", "os-cygwin" ),
( "osdep/glob-win.c", "glob-win32-replacement" ),