blob: 6db9d5ae7da9bb387d759d46c28aaac2d77ee4ab [file]
/*
Copyright (c) 2011, 2014 mingw-w64 project
Copyright (c) 2015 Intel Corporation
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <malloc.h>
#include <stdio.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define WINPTHREAD_MUTEX_DECL WINPTHREAD_API
/* public header files */
#include "pthread.h"
/* internal header files */
#include "misc.h"
/**
* Reference:
*
* pthread_mutex_init(), pthread_mutex_destroy():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutex_destroy.html>
*
* pthread_mutex_lock(), pthread_mutex_trylock(), pthread_mutex_unlock():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutex_lock.html>
*
* pthread_mutex_timedlock(), pthread_mutex_clocklock():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutex_clocklock.html>
*
* pthread_mutex_getprioceiling(), pthread_mutex_setprioceiling():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutex_getprioceiling.html>
*
* pthread_mutex_consistent():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutex_consistent.html>
*
* pthread_mutexattr_init(), pthread_mutexattr_destroy():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_destroy.html>
*
* pthread_mutexattr_gettype(), pthread_mutexattr_settype():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_gettype.html>
*
* pthread_mutexattr_getpshared(), pthread_mutexattr_setpshared():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_getpshared.html>
*
* pthread_mutexattr_getprotocol(), pthread_mutexattr_setprotocol():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_getprotocol.html>
*
* pthread_mutexattr_getprioceiling(), pthread_mutexattr_setprioceiling():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_getprioceiling.html>
*
* pthread_mutexattr_getrobust(), pthread_mutexattr_setrobust():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_mutexattr_getrobust.html>
*
* The following functions are not implemented:
*
* - pthread_mutex_clocklock()
* - pthread_mutex_getprioceiling()
* - pthread_mutex_setprioceiling()
* - pthread_mutex_consistent()
* - pthread_mutexattr_getrobust()
* - pthread_mutexattr_setrobust()
*/
typedef enum {
Unlocked, /* Not locked. */
Locked, /* Locked but without waiters. */
Waiting, /* Locked, may have waiters. */
} mutex_state_t;
typedef enum {
Normal,
Errorcheck,
Recursive,
} mutex_type_t;
/* The heap-allocated part of a mutex. */
typedef struct {
mutex_state_t state;
mutex_type_t type;
HANDLE event; /* Auto-reset event. */
unsigned rec_lock; /* For recursive mutexes, the number of times the
mutex has been locked in excess by the same thread. */
volatile DWORD owner; /* For recursive and error-checking mutexes, the
ID of the owning thread if the mutex is locked. */
} mutex_impl_t;
/**
* Evaluates to non-zero if `m` is a static initializer for `pthread_mutex_t`:
*
* PTHREAD_DEFAULT_MUTEX_INITIALIZER: -1
* PTHREAD_NORMAL_MUTEX_INITIALIZER: -1
* PTHREAD_ERRORCHECK_MUTEX_INITIALIZER: -2
* PTHREAD_RECURSIVE_MUTEX_INITIALIZER: -3
*/
#define STATIC_MUTEX_INITIALIZER(m) ((uintptr_t)(m) >= (uintptr_t)-3)
/**
* Obtain pointer to `mutex_impl_t` structure pointed to by `m`.
*
* If `m` points to statically initialzied `pthread_mutex_t` object,
* allocate `mutex_impl_t` structure and store its address in `*m`.
*
* On success, stores pointer to `mutex_impl_t` structure in `*mi`.
*
* Returns zero on success and an error-code on failure.
*/
static WINPTHREADS_INLINE int mutex_impl(pthread_mutex_t *m, mutex_impl_t **mi)
{
*mi = (mutex_impl_t *)*m;
/**
* We need to avoid race condition when more than one thread attempts to use
* same statically initialized `pthread_mutex_t` object at the same time.
*
* Store newly initialized mutex in `mi`, which is a local variable supplied
* by the caller, and only then store it in `m`.
*
* If some other thread was faster then us, destroy newly created mutex
* and use mutex pointed to by `m`.
*/
if (unlikely (STATIC_MUTEX_INITIALIZER (*mi))) {
pthread_mutexattr_t mutexAttr;
int mutexType;
mutex_impl_t *volatile initializer = *mi;
switch ((pthread_mutex_t)initializer) {
case PTHREAD_NORMAL_MUTEX_INITIALIZER:
mutexType = PTHREAD_MUTEX_NORMAL;
break;
case PTHREAD_ERRORCHECK_MUTEX_INITIALIZER:
mutexType = PTHREAD_MUTEX_ERRORCHECK;
break;
case PTHREAD_RECURSIVE_MUTEX_INITIALIZER:
mutexType = PTHREAD_MUTEX_RECURSIVE;
break;
default:
UNREACHABLE ();
}
int error_code = pthread_mutexattr_init (&mutexAttr);
if (error_code) {
return error_code;
}
pthread_mutexattr_settype (&mutexAttr, mutexType);
error_code = pthread_mutex_init ((pthread_mutex_t *)mi, &mutexAttr);
pthread_mutexattr_destroy (&mutexAttr);
if (error_code) {
return error_code;
}
void *mutex = InterlockedCompareExchangePointer ((void **)m, *mi, initializer);
/**
* Some other thread was faster than us.
*/
if (unlikely (mutex != initializer)) {
pthread_mutex_destroy ((pthread_mutex_t *)mi);
*mi = mutex;
}
}
if (unlikely (*mi == NULL)) {
return EINVAL;
}
return 0;
}
/* Lock a mutex. Give up after 'timeout' ms (with ETIMEDOUT),
or never if timeout=INFINITE. */
static WINPTHREADS_INLINE int pthread_mutex_lock_intern(pthread_mutex_t *m, DWORD timeout)
{
mutex_impl_t *mi = NULL;
int error_code = mutex_impl (m, &mi);
if (error_code) {
return error_code;
}
mutex_state_t old_state = InterlockedExchange((long *)&mi->state, Locked);
if (unlikely(old_state != Unlocked)) {
/* The mutex is already locked. */
if (mi->type != Normal) {
/* Recursive or Errorcheck */
if (mi->owner == GetCurrentThreadId()) {
/* FIXME: A recursive mutex should not need two atomic ops when locking
recursively. We could rewrite by doing compare-and-swap instead of
test-and-set the first time, but it would lead to more code
duplication and add a conditional branch to the critical path. */
InterlockedCompareExchange((long *)&mi->state, old_state, Locked);
if (mi->type == Recursive) {
mi->rec_lock++;
return 0;
} else {
/* type == Errorcheck */
return EDEADLK;
}
}
}
while (InterlockedExchange((long *)&mi->state, Waiting) != Unlocked) {
/* For timed locking attempts, it is possible (although unlikely)
that we are woken up but someone else grabs the lock before us,
and we have to go back to sleep again. In that case, the total
wait may be longer than expected. */
unsigned r = _pthread_wait_for_single_object(mi->event, timeout);
switch (r) {
case WAIT_TIMEOUT:
return ETIMEDOUT;
case WAIT_OBJECT_0:
break;
default:
return EINVAL;
}
}
}
if (mi->type != Normal)
mi->owner = GetCurrentThreadId();
return 0;
}
int pthread_mutex_lock(pthread_mutex_t *m)
{
return pthread_mutex_lock_intern (m, INFINITE);
}
int pthread_mutex_timedlock64(pthread_mutex_t *m, const struct _timespec64 *ts)
{
/**
* The pthread_mutex_timedlock() function shall fail if:
*
* [EINVAL]
* The process or thread would have blocked, and the abstime parameter
* specified a nanoseconds field value less than zero or greater than
* or equal to 1000 million.
*/
if (ts->tv_nsec < 0 || ts->tv_nsec >= 1000000000) {
return EINVAL;
}
unsigned __int64 end = _pthread_time_in_ms_from_timespec (ts);
unsigned __int64 now = _pthread_time_in_ms ();
unsigned __int64 patience = end > now ? end - now : 0;
if (patience > 0xffffffff) {
patience = INFINITE;
}
return pthread_mutex_lock_intern(m, (DWORD) patience);
}
int pthread_mutex_timedlock32(pthread_mutex_t *m, const struct _timespec32 *ts)
{
struct _timespec64 ts64 = {.tv_sec = ts->tv_sec, .tv_nsec = ts->tv_nsec};
return pthread_mutex_timedlock64 (m, &ts64);
}
int pthread_mutex_unlock(pthread_mutex_t *m)
{
mutex_impl_t *mi = NULL;
int error_code = mutex_impl (m, &mi);
if (error_code) {
return error_code;
}
if (unlikely(mi->type != Normal)) {
if (mi->state == Unlocked)
return EPERM;
if (mi->owner != GetCurrentThreadId())
return EPERM;
if (mi->rec_lock > 0) {
mi->rec_lock--;
return 0;
}
mi->owner = (DWORD)-1;
}
if (unlikely(InterlockedExchange((long *)&mi->state, Unlocked) == Waiting)) {
if (!SetEvent(mi->event))
return EINVAL;
}
return 0;
}
int pthread_mutex_trylock(pthread_mutex_t *m)
{
mutex_impl_t *mi = NULL;
int error_code = mutex_impl (m, &mi);
if (error_code) {
return error_code;
}
if (InterlockedCompareExchange((long *)&mi->state, Locked, Unlocked) == Unlocked) {
if (mi->type != Normal)
mi->owner = GetCurrentThreadId();
return 0;
} else {
if (mi->type == Recursive && mi->owner == GetCurrentThreadId()) {
mi->rec_lock++;
return 0;
}
return EBUSY;
}
}
int pthread_mutex_init(pthread_mutex_t *m, const pthread_mutexattr_t *a)
{
int pshared = PTHREAD_PROCESS_PRIVATE;
int type = PTHREAD_MUTEX_DEFAULT;
if (a != NULL) {
/**
* POSIX:
*
* If an implementation detects that the value specified by the attr
* argument to pthread_mutex_init() does not refer to an initialized mutex
* attributes object, it is recommended that the function should fail and
* report an [EINVAL] error.
*/
if (pthread_mutexattr_getpshared (a, &pshared) != 0) {
return EINVAL;
}
if (pthread_mutexattr_gettype (a, &type) != 0) {
return EINVAL;
}
if (pshared == PTHREAD_PROCESS_SHARED) {
return ENOSYS;
}
}
mutex_impl_t *mi = calloc (1, sizeof (mutex_impl_t));
/**
* The pthread_mutex_init() function shall fail if:
*
* [ENOMEM]
* Insufficient memory exists to initialize the mutex.
*/
if (mi == NULL) {
return ENOMEM;
}
mi->event = CreateEventW (NULL, FALSE, FALSE, NULL);
/**
* The pthread_mutex_init() function shall fail if:
*
* [EAGAIN]
* The system lacked the necessary resources (other than memory) to
* initialize another mutex.
*/
if (mi->event == NULL) {
free (mi);
return EAGAIN;
}
switch (type) {
case PTHREAD_MUTEX_RECURSIVE:
mi->type = Recursive;
break;
case PTHREAD_MUTEX_ERRORCHECK:
mi->type = Errorcheck;
break;
case PTHREAD_MUTEX_NORMAL:
mi->type = Normal;
break;
default:
UNREACHABLE ();
}
mi->owner = (DWORD)-1;
mi->state = Unlocked;
mi->rec_lock = 0;
*m = (pthread_mutex_t)mi;
return 0;
}
int pthread_mutex_destroy(pthread_mutex_t *m)
{
mutex_impl_t *mi = (mutex_impl_t *)*m;
if (!STATIC_MUTEX_INITIALIZER(mi)) {
CloseHandle(mi->event);
free(mi);
/* Sabotage attempts to re-use the mutex before initialising it again. */
*m = (pthread_mutex_t)NULL;
}
return 0;
}
int pthread_mutexattr_init(pthread_mutexattr_t *a)
{
*a = PTHREAD_MUTEX_NORMAL | (PTHREAD_PROCESS_PRIVATE << 3);
return 0;
}
int pthread_mutexattr_destroy(pthread_mutexattr_t *a)
{
if (!a)
return EINVAL;
return 0;
}
int pthread_mutexattr_gettype(const pthread_mutexattr_t *a, int *type)
{
if (!a || !type)
return EINVAL;
*type = *a & 3;
return 0;
}
int pthread_mutexattr_settype(pthread_mutexattr_t *a, int type)
{
if (!a)
return EINVAL;
if (type != PTHREAD_MUTEX_NORMAL && type != PTHREAD_MUTEX_RECURSIVE && type != PTHREAD_MUTEX_ERRORCHECK)
return EINVAL;
*a &= ~3;
*a |= type;
return 0;
}
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *a, int *type)
{
if (!a || !type)
return EINVAL;
*type = (*a & 4 ? PTHREAD_PROCESS_SHARED : PTHREAD_PROCESS_PRIVATE);
return 0;
}
int pthread_mutexattr_setpshared(pthread_mutexattr_t *a, int type)
{
int r = 0;
if (!a)
return EINVAL;
if (type != PTHREAD_PROCESS_SHARED && type != PTHREAD_PROCESS_PRIVATE)
return EINVAL;
if (type == PTHREAD_PROCESS_SHARED) {
type = PTHREAD_PROCESS_PRIVATE;
r = ENOSYS;
}
type = (type == PTHREAD_PROCESS_SHARED ? 4 : 0);
*a &= ~4;
*a |= type;
return r;
}
int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *a, int *type)
{
*type = *a & (8 + 16);
return 0;
}
int pthread_mutexattr_setprotocol(pthread_mutexattr_t *a, int type)
{
if ((type & (8 + 16)) != 8 + 16)
return EINVAL;
*a &= ~(8 + 16);
*a |= type;
return 0;
}
int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *a, int *prio)
{
*prio = *a / PTHREAD_PRIO_MULT;
return 0;
}
int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *a, int prio)
{
*a &= (PTHREAD_PRIO_MULT - 1);
*a += prio * PTHREAD_PRIO_MULT;
return 0;
}