382 lines
10 KiB
C
382 lines
10 KiB
C
/*
|
|
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.
|
|
*/
|
|
|
|
#include <windows.h>
|
|
#include <stdio.h>
|
|
#include <malloc.h>
|
|
#include <stdbool.h>
|
|
#include "pthread.h"
|
|
#include "misc.h"
|
|
|
|
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, or NULL if not yet allocated. */
|
|
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;
|
|
|
|
/* Whether a mutex is still a static initializer (not a pointer to
|
|
a mutex_impl_t). */
|
|
static bool
|
|
is_static_initializer(pthread_mutex_t m)
|
|
{
|
|
/* Treat 0 as a static initializer as well (for normal mutexes),
|
|
to tolerate sloppy code in libgomp. (We should rather fix that code!) */
|
|
intptr_t v = (intptr_t)m;
|
|
return v >= -3 && v <= 0;
|
|
/* Should be simple:
|
|
return (uintptr_t)m >= (uintptr_t)-3; */
|
|
}
|
|
|
|
/* Create and return the implementation part of a mutex from a static
|
|
initialiser. Return NULL on out-of-memory error. */
|
|
static WINPTHREADS_ATTRIBUTE((noinline)) mutex_impl_t *
|
|
mutex_impl_init(pthread_mutex_t *m, mutex_impl_t *mi)
|
|
{
|
|
mutex_impl_t *new_mi = malloc(sizeof(mutex_impl_t));
|
|
if (new_mi == NULL)
|
|
return NULL;
|
|
new_mi->state = Unlocked;
|
|
new_mi->type = (mi == (void *)PTHREAD_RECURSIVE_MUTEX_INITIALIZER ? Recursive
|
|
: mi == (void *)PTHREAD_ERRORCHECK_MUTEX_INITIALIZER ? Errorcheck
|
|
: Normal);
|
|
new_mi->event = NULL;
|
|
new_mi->rec_lock = 0;
|
|
new_mi->owner = (DWORD)-1;
|
|
if (InterlockedCompareExchangePointer((PVOID volatile *)m, new_mi, mi) == mi) {
|
|
return new_mi;
|
|
} else {
|
|
/* Someone created the struct before us. */
|
|
free(new_mi);
|
|
return (mutex_impl_t *)*m;
|
|
}
|
|
}
|
|
|
|
/* Return the implementation part of a mutex, creating it if necessary.
|
|
Return NULL on out-of-memory error. */
|
|
static inline mutex_impl_t *
|
|
mutex_impl(pthread_mutex_t *m)
|
|
{
|
|
mutex_impl_t *mi = (mutex_impl_t *)*m;
|
|
if (is_static_initializer((pthread_mutex_t)mi)) {
|
|
return mutex_impl_init(m, mi);
|
|
} else {
|
|
/* mi cannot be null here; avoid a test in the fast path. */
|
|
if (mi == NULL)
|
|
UNREACHABLE();
|
|
return mi;
|
|
}
|
|
}
|
|
|
|
/* Lock a mutex. Give up after 'timeout' ms (with ETIMEDOUT),
|
|
or never if timeout=INFINITE. */
|
|
static inline int
|
|
pthread_mutex_lock_intern (pthread_mutex_t *m, DWORD timeout)
|
|
{
|
|
mutex_impl_t *mi = mutex_impl(m);
|
|
if (mi == NULL)
|
|
return ENOMEM;
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Make sure there is an event object on which to wait. */
|
|
if (mi->event == NULL) {
|
|
/* Make an auto-reset event object. */
|
|
HANDLE ev = CreateEvent(NULL, false, false, NULL);
|
|
if (ev == NULL) {
|
|
switch (GetLastError()) {
|
|
case ERROR_ACCESS_DENIED:
|
|
return EPERM;
|
|
default:
|
|
return ENOMEM; /* Probably accurate enough. */
|
|
}
|
|
}
|
|
if (InterlockedCompareExchangePointer(&mi->event, ev, NULL) != NULL) {
|
|
/* Someone created the event before us. */
|
|
CloseHandle(ev);
|
|
}
|
|
}
|
|
|
|
/* At this point, mi->event is non-NULL. */
|
|
|
|
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_timedlock(pthread_mutex_t *m, const struct timespec *ts)
|
|
{
|
|
unsigned long long patience;
|
|
if (ts != NULL) {
|
|
unsigned long long end = _pthread_time_in_ms_from_timespec(ts);
|
|
unsigned long long now = _pthread_time_in_ms();
|
|
patience = end > now ? end - now : 0;
|
|
if (patience > 0xffffffff)
|
|
patience = INFINITE;
|
|
} else {
|
|
patience = INFINITE;
|
|
}
|
|
return pthread_mutex_lock_intern(m, patience);
|
|
}
|
|
|
|
int pthread_mutex_unlock(pthread_mutex_t *m)
|
|
{
|
|
/* Here m might an initialiser of an error-checking or recursive mutex, in
|
|
which case the behaviour is well-defined, so we can't skip this check. */
|
|
mutex_impl_t *mi = mutex_impl(m);
|
|
if (mi == NULL)
|
|
return ENOMEM;
|
|
|
|
if (unlikely(mi->type != Normal)) {
|
|
if (mi->state == Unlocked)
|
|
return EINVAL;
|
|
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 EPERM;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int pthread_mutex_trylock(pthread_mutex_t *m)
|
|
{
|
|
mutex_impl_t *mi = mutex_impl(m);
|
|
if (mi == NULL)
|
|
return ENOMEM;
|
|
|
|
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)
|
|
{
|
|
pthread_mutex_t init = PTHREAD_MUTEX_INITIALIZER;
|
|
if (a != NULL) {
|
|
int pshared;
|
|
if (pthread_mutexattr_getpshared(a, &pshared) == 0
|
|
&& pshared == PTHREAD_PROCESS_SHARED)
|
|
return ENOSYS;
|
|
|
|
int type;
|
|
if (pthread_mutexattr_gettype(a, &type) == 0) {
|
|
switch (type) {
|
|
case PTHREAD_MUTEX_ERRORCHECK:
|
|
init = PTHREAD_ERRORCHECK_MUTEX_INITIALIZER;
|
|
break;
|
|
case PTHREAD_MUTEX_RECURSIVE:
|
|
init = PTHREAD_RECURSIVE_MUTEX_INITIALIZER;
|
|
break;
|
|
default:
|
|
init = PTHREAD_MUTEX_INITIALIZER;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
*m = init;
|
|
return 0;
|
|
}
|
|
|
|
int pthread_mutex_destroy (pthread_mutex_t *m)
|
|
{
|
|
mutex_impl_t *mi = (mutex_impl_t *)*m;
|
|
if (!is_static_initializer((pthread_mutex_t)mi)) {
|
|
if (mi->event != NULL)
|
|
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 || (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 || (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;
|
|
}
|