Branch :
/* $OpenBSD: rthread_mutex.c,v 1.8 2026/03/27 12:26:58 claudio Exp $ */
/*
* Copyright (c) 2017 Martin Pieuchot <mpi@openbsd.org>
* Copyright (c) 2012 Philip Guenther <guenther@openbsd.org>
*
* Permission to use, copy, modify, and 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 <errno.h>
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "rthread.h"
#include "cancel.h"
#include "synch.h"
/*
* States defined in "Futexes Are Tricky" 5.2
*/
enum {
UNLOCKED = 0,
LOCKED = 1, /* locked without waiter */
CONTENDED = 2, /* threads waiting for this mutex */
};
#define SPIN_COUNT 128
#if defined(__i386__) || defined(__amd64__)
#define SPIN_WAIT() asm volatile("pause": : : "memory")
#else
#define SPIN_WAIT() do { } while (0)
#endif
static struct __cmtx static_init_lock = __CMTX_INITIALIZER();
int
pthread_mutex_init(pthread_mutex_t *mutexp, const pthread_mutexattr_t *attr)
{
pthread_mutex_t mutex;
mutex = calloc(1, sizeof(*mutex));
if (mutex == NULL)
return (ENOMEM);
if (attr == NULL) {
mutex->type = PTHREAD_MUTEX_DEFAULT;
mutex->prioceiling = -1;
} else {
mutex->type = (*attr)->ma_type;
mutex->prioceiling = (*attr)->ma_protocol ==
PTHREAD_PRIO_PROTECT ? (*attr)->ma_prioceiling : -1;
}
*mutexp = mutex;
return (0);
}
DEF_STRONG(pthread_mutex_init);
int
pthread_mutex_destroy(pthread_mutex_t *mutexp)
{
pthread_mutex_t mutex;
if (mutexp == NULL)
return (EINVAL);
mutex = *mutexp;
if (mutex) {
if (mutex->lock != UNLOCKED) {
#define MSG "pthread_mutex_destroy on mutex with waiters!\n"
write(2, MSG, sizeof(MSG) - 1);
#undef MSG
return (EBUSY);
}
free((void *)mutex);
*mutexp = NULL;
}
return (0);
}
DEF_STRONG(pthread_mutex_destroy);
static int
_rthread_mutex_trylock(pthread_mutex_t mutex, int trywait,
const struct timespec *abs)
{
pthread_t self = pthread_self();
if (atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED) == UNLOCKED) {
membar_enter_after_atomic();
mutex->owner = self;
return (0);
}
if (mutex->owner == self) {
int type = mutex->type;
/* already owner? handle recursive behavior */
if (type != PTHREAD_MUTEX_RECURSIVE) {
if (trywait || type == PTHREAD_MUTEX_ERRORCHECK)
return (trywait ? EBUSY : EDEADLK);
/* self-deadlock is disallowed by strict */
if (type == PTHREAD_MUTEX_STRICT_NP && abs == NULL)
abort();
/* self-deadlock, possibly until timeout */
while (_twait(&mutex->type, type, CLOCK_REALTIME,
abs) != ETIMEDOUT)
;
return (ETIMEDOUT);
} else {
if (mutex->count == INT_MAX)
return (EAGAIN);
mutex->count++;
return (0);
}
}
return (EBUSY);
}
static int
_rthread_mutex_timedlock(pthread_mutex_t *mutexp, int trywait,
const struct timespec *abs, int timed)
{
pthread_t self = pthread_self();
pthread_mutex_t mutex;
unsigned int i, lock;
int error = 0;
if (mutexp == NULL)
return (EINVAL);
/*
* If the mutex is statically initialized, perform the dynamic
* initialization. Note: _thread_mutex_lock() in libc requires
* pthread_mutex_lock() to perform the mutex init when *mutexp
* is NULL.
*/
if (*mutexp == NULL) {
__cmtx_enter(&static_init_lock);
if (*mutexp == NULL)
error = pthread_mutex_init(mutexp, NULL);
__cmtx_leave(&static_init_lock);
if (error != 0)
return (EINVAL);
}
mutex = *mutexp;
_rthread_debug(5, "%p: mutex_%slock %p (%p)\n", self,
(timed ? "timed" : (trywait ? "try" : "")), (void *)mutex,
(void *)mutex->owner);
error = _rthread_mutex_trylock(mutex, trywait, abs);
if (error != EBUSY || trywait)
return (error);
/* Try hard to not enter the kernel. */
for (i = 0; i < SPIN_COUNT; i++) {
if (mutex->lock == UNLOCKED)
break;
SPIN_WAIT();
}
lock = atomic_cas_uint(&mutex->lock, UNLOCKED, LOCKED);
if (lock == UNLOCKED) {
membar_enter_after_atomic();
mutex->owner = self;
return (0);
}
if (lock != CONTENDED) {
/* Indicate that we're waiting on this mutex. */
lock = atomic_swap_uint(&mutex->lock, CONTENDED);
}
while (lock != UNLOCKED) {
error = _twait(&mutex->lock, CONTENDED, CLOCK_REALTIME, abs);
if (error == ETIMEDOUT)
return (error);
/*
* We cannot know if there's another waiter, so in
* doubt set the state to CONTENDED.
*/
lock = atomic_swap_uint(&mutex->lock, CONTENDED);
}
membar_enter_after_atomic();
mutex->owner = self;
return (0);
}
int
pthread_mutex_trylock(pthread_mutex_t *mutexp)
{
return (_rthread_mutex_timedlock(mutexp, 1, NULL, 0));
}
int
pthread_mutex_timedlock(pthread_mutex_t *mutexp, const struct timespec *abs)
{
return (_rthread_mutex_timedlock(mutexp, 0, abs, 1));
}
int
pthread_mutex_lock(pthread_mutex_t *mutexp)
{
return (_rthread_mutex_timedlock(mutexp, 0, NULL, 0));
}
DEF_STRONG(pthread_mutex_lock);
int
pthread_mutex_unlock(pthread_mutex_t *mutexp)
{
pthread_t self = pthread_self();
pthread_mutex_t mutex;
if (mutexp == NULL)
return (EINVAL);
if (*mutexp == NULL)
#if PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_ERRORCHECK
return (EPERM);
#elif PTHREAD_MUTEX_DEFAULT == PTHREAD_MUTEX_NORMAL
return(0);
#else
abort();
#endif
mutex = *mutexp;
_rthread_debug(5, "%p: mutex_unlock %p (%p)\n", self, (void *)mutex,
(void *)mutex->owner);
if (mutex->owner != self) {
_rthread_debug(5, "%p: different owner %p (%p)\n", self, (void *)mutex,
(void *)mutex->owner);
if (mutex->type == PTHREAD_MUTEX_ERRORCHECK ||
mutex->type == PTHREAD_MUTEX_RECURSIVE) {
return (EPERM);
} else {
/*
* For mutex type NORMAL our undefined behavior for
* unlocking an unlocked mutex is to succeed without
* error. All other undefined behaviors are to
* abort() immediately.
*/
if (mutex->owner == NULL &&
mutex->type == PTHREAD_MUTEX_NORMAL)
return (0);
else
abort();
}
}
if (mutex->type == PTHREAD_MUTEX_RECURSIVE) {
if (mutex->count > 0) {
mutex->count--;
return (0);
}
}
mutex->owner = NULL;
membar_exit_before_atomic();
if (atomic_dec_int_nv(&mutex->lock) != UNLOCKED) {
mutex->lock = UNLOCKED;
_wake(&mutex->lock, 1);
}
return (0);
}
DEF_STRONG(pthread_mutex_unlock);