/*
* Copyright (C) Igor Sysoev
* Copyright (C) NGINX, Inc.
*/
#include <nxt_main.h>
#if (NXT_HAVE_SEM_TIMEDWAIT)
/*
* Linux POSIX semaphores use atomic/futex operations in since glibc 2.3.
*
* FreeBSD has two POSIX semaphore implementations. The first implementation
* has been introduced in FreeBSD 5.0 but it has some drawbacks:
* 1) it had a bug (http://bugs.freebsd.org/127545) fixed in FreeBSD 7.2;
* 2) it does not use atomic operations and always calls ksem syscalls;
* 3) a number of semaphores is just 30 by default and until FreeBSD 8.1
* the number cannot be changed after boot time.
*
* The second implementation has been introduced in FreeBSD 6.1 in libthr
* and uses atomic operations and umtx syscall. However, until FreeBSD 9.0
* a choice of implementation depended on linking order of libthr and libc.
* In FreeBSD 9.0 the umtx implementation has been moved to libc.
*
* Solaris have POSIX semaphores.
*
* MacOSX has limited POSIX semaphore implementation:
* 1) sem_init() exists but returns ENOSYS;
* 2) no sem_timedwait().
*/
nxt_int_t
nxt_sem_init(nxt_sem_t *sem, nxt_uint_t count)
{
if (sem_init(sem, 0, count) == 0) {
nxt_thread_log_debug("sem_init(%p)", sem);
return NXT_OK;
}
nxt_thread_log_alert("sem_init(%p) failed %E", sem, nxt_errno);
return NXT_ERROR;
}
void
nxt_sem_destroy(nxt_sem_t *sem)
{
if (sem_destroy(sem) == 0) {
nxt_thread_log_debug("sem_destroy(%p)", sem);
return;
}
nxt_thread_log_alert("sem_destroy(%p) failed %E", sem, nxt_errno);
}
nxt_int_t
nxt_sem_post(nxt_sem_t *sem)
{
nxt_thread_log_debug("sem_post(%p)", sem);
if (nxt_fast_path(sem_post(sem) == 0)) {
return NXT_OK;
}
nxt_thread_log_alert("sem_post(%p) failed %E", sem, nxt_errno);
return NXT_ERROR;
}
nxt_err_t
nxt_sem_wait(nxt_sem_t *sem, nxt_nsec_t timeout)
{
int n;
nxt_err_t err;
nxt_nsec_t ns;
nxt_thread_t *thr;
nxt_realtime_t *now;
struct timespec ts;
thr = nxt_thread();
if (timeout == NXT_INFINITE_NSEC) {
nxt_log_debug(thr->log, "sem_wait(%p) enter", sem);
for ( ;; ) {
n = sem_wait(sem);
err = nxt_errno;
nxt_thread_time_update(thr);
if (nxt_fast_path(n == 0)) {
nxt_thread_log_debug("sem_wait(%p) exit", sem);
return 0;
}
switch (err) {
case NXT_EINTR:
nxt_log_error(NXT_LOG_INFO, thr->log, "sem_wait(%p) failed %E",
sem, err);
continue;
default:
nxt_log_alert(thr->log, "sem_wait(%p) failed %E", sem, err);
return err;
}
}
}
#if (NXT_HAVE_SEM_TRYWAIT_FAST)
nxt_log_debug(thr->log, "sem_trywait(%p) enter", sem);
/*
* Fast sem_trywait() using atomic operations may eliminate
* timeout processing.
*/
if (nxt_fast_path(sem_trywait(sem) == 0)) {
return 0;
}
#endif
nxt_log_debug(thr->log, "sem_timedwait(%p, %N) enter", sem, timeout);
now = nxt_thread_realtime(thr);
ns = now->nsec + timeout;
ts.tv_sec = now->sec + ns / 1000000000;
ts.tv_nsec = ns % 1000000000;
for ( ;; ) {
n = sem_timedwait(sem, &ts);
err = nxt_errno;
nxt_thread_time_update(thr);
if (nxt_fast_path(n == 0)) {
nxt_thread_log_debug("sem_timedwait(%p) exit", sem);
return 0;
}
switch (err) {
case NXT_ETIMEDOUT:
nxt_log_debug(thr->log, "sem_timedwait(%p) exit: %d", sem, err);
return err;
case NXT_EINTR:
nxt_log_error(NXT_LOG_INFO, thr->log, "sem_timedwait(%p) failed %E",
sem, err);
continue;
default:
nxt_log_alert(thr->log, "sem_timedwait(%p) failed %E", sem, err);
return err;
}
}
}
#else
/* Semaphore implementation using pthread conditional variable. */
nxt_int_t
nxt_sem_init(nxt_sem_t *sem, nxt_uint_t count)
{
if (nxt_thread_mutex_create(&sem->mutex) == NXT_OK) {
if (nxt_thread_cond_create(&sem->cond) == NXT_OK) {
sem->count = count;
return NXT_OK;
}
nxt_thread_mutex_destroy(&sem->mutex);
}
return NXT_ERROR;
}
void
nxt_sem_destroy(nxt_sem_t *sem)
{
nxt_thread_cond_destroy(&sem->cond);
nxt_thread_mutex_destroy(&sem->mutex);
}
nxt_int_t
nxt_sem_post(nxt_sem_t *sem)
{
nxt_int_t ret;
if (nxt_slow_path(nxt_thread_mutex_lock(&sem->mutex) != NXT_OK)) {
return NXT_ERROR;
}
ret = nxt_thread_cond_signal(&sem->cond);
sem->count++;
/* NXT_ERROR overrides NXT_OK. */
return (nxt_thread_mutex_unlock(&sem->mutex) | ret);
}
nxt_err_t
nxt_sem_wait(nxt_sem_t *sem, nxt_nsec_t timeout)
{
nxt_err_t err;
err = 0;
if (nxt_slow_path(nxt_thread_mutex_lock(&sem->mutex) != NXT_OK)) {
return NXT_ERROR;
}
while (sem->count == 0) {
err = nxt_thread_cond_wait(&sem->cond, &sem->mutex, timeout);
if (err != 0) {
goto error;
}
}
sem->count--;
error:
/* NXT_ERROR overrides NXT_OK and NXT_ETIMEDOUT. */
return (nxt_thread_mutex_unlock(&sem->mutex) | err);
}
#endif