blob: c6648bc825c26ab0610309704b6cc6bf0df52c2d [file]
/*
Copyright (c) 2026 mingw-w64 project
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 <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
/* public header files */
#include "pthread.h"
/* internal header files */
#include "misc.h"
/**
* Reference:
*
* pthread_spin_init(), pthread_spin_destroy():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_spin_destroy.html>
*
* pthread_spin_lock(), pthread_spin_trylock():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_spin_lock.html>
*
* pthread_spin_unlock():
* <https://pubs.opengroup.org/onlinepubs/9799919799.2024edition/functions/pthread_spin_unlock.html>
*/
/**
* Internal structure pointed to by `pthread_spinlock_t` objects.
*/
typedef struct {
/**
* This value is used to indicate that spin lock has no owner.
*/
#define THREAD_ID_NO_OWNER ((DWORD)-1)
/**
* ID of the thread which holds the lock.
*/
DWORD ThreadId;
/**
* Auto-reset event.
*
* This event is created in signaled state, which means any thread can
* `WaitForSingleObject` on it; only one thread will be released at a time,
* after which this event will be automatically put into non-signaled state.
*
* The released thread owns the lock; it unlocks it by calling `SetEvent` on
* this event, which puts this event into signaled state, allowing system
* release another thread which waits on it.
*
* The cycle repeats until this event is destroyed.
*/
HANDLE Event;
} WinpthreadsSpinlock;
#define WINPTHREADS_SPINLOCK_INITIALIZER ((WinpthreadsSpinlock *) PTHREAD_SPINLOCK_INITIALIZER)
/**
* Obtain pointer to `WinpthreadsSpinlock` structure pointed to by `lock`.
*
* If `lock` points to statically initialzied `pthread_spinlock_t` object,
* allocate `WinpthreadsSpinlock` structure and store its address in `*lock`.
*
* On success, stores pointer to `WinpthreadsSpinlock` structure in `*spinlock`.
*
* Returns zero on success and an error-code on failure.
*/
static WINPTHREADS_INLINE int WinpthreadsSpinlockGet (pthread_spinlock_t *lock, WinpthreadsSpinlock **spinlock)
{
WinpthreadsSpinlock **pSpinlock = (WinpthreadsSpinlock **) lock;
/**
* POSIX:
*
* If an implementation detects that the value specified by the lock argument
* to pthread_spin_*() does not refer to an initialized spin lock object,
* it is recommended that the function should fail and report an [EINVAL] error.
*/
if (unlikely (pSpinlock == NULL)) {
return EINVAL;
}
if (*pSpinlock == WINPTHREADS_SPINLOCK_INITIALIZER) {
/**
* We need to avoid race condition when more than one thread calls
* `pthread_spin_[try]lock` on statically initialized `pthread_spinlock_t`
* at the same time.
*
* Store newly initialized spinlock in `spinlock`, which points to local
* variable supplied by the caller, and then store it in `*lock`.
*
* If some other thread was faster then us, destroy newly created spinlock
* and use spinlock pointed to by `lock`.
*/
int error_code = pthread_spin_init ((pthread_spinlock_t *) spinlock, PTHREAD_PROCESS_PRIVATE);
if (error_code) {
return error_code;
}
WinpthreadsSpinlock *oldLock = (WinpthreadsSpinlock *) InterlockedCompareExchangePointer (
(void **) pSpinlock, *spinlock, WINPTHREADS_SPINLOCK_INITIALIZER
);
/**
* Some other thread was faster than us.
*/
if (unlikely (oldLock != WINPTHREADS_SPINLOCK_INITIALIZER)) {
pthread_spin_destroy ((pthread_spinlock_t *) spinlock);
*spinlock = oldLock;
}
} else {
*spinlock = *pSpinlock;
}
if (unlikely (*spinlock == NULL)) {
return EINVAL;
}
return 0;
}
int pthread_spin_init (pthread_spinlock_t *lock, int pshared)
{
WinpthreadsSpinlock **pSpinlock = (WinpthreadsSpinlock **) lock;
if (unlikely (pSpinlock == NULL)) {
return EINVAL;
}
if (unlikely (pshared != PTHREAD_PROCESS_PRIVATE && pshared != PTHREAD_PROCESS_SHARED)) {
return EINVAL;
}
if (unlikely (pshared == PTHREAD_PROCESS_SHARED)) {
return ENOSYS;
}
WinpthreadsSpinlock *wSpinlock = calloc (1, sizeof (WinpthreadsSpinlock));
/**
* The pthread_spin_init() function shall fail if:
*
* [ENOMEM]
* Insufficient memory exists to initialize the lock.
*/
if (wSpinlock == NULL) {
return ENOMEM;
}
wSpinlock->ThreadId = THREAD_ID_NO_OWNER;
wSpinlock->Event = CreateEventA (NULL, FALSE, TRUE, NULL);
/**
* The pthread_spin_init() function shall fail if:
*
* [EAGAIN]
* The system lacks the necessary resources to initialize another spin lock.
*/
if (wSpinlock->Event == NULL) {
free (wSpinlock);
return EAGAIN;
}
*pSpinlock = wSpinlock;
return 0;
}
int pthread_spin_destroy (pthread_spinlock_t *lock)
{
WinpthreadsSpinlock **pSpinlock = (WinpthreadsSpinlock **) lock;
/**
* POSIX:
*
* If an implementation detects that the value specified by the lock argument
* to pthread_spin_destroy() does not refer to an initialized spin lock object,
* it is recommended that the function should fail and report an [EINVAL] error.
*/
if (unlikely (pSpinlock == NULL)) {
return EINVAL;
}
/**
* If `lock` points to statically initialized `pthread_spinlock_t` object,
* this will immediately invalidate it, minimizing the window for
* `pthread_spin_lock` and `pthread_spin_trylock` to attempt using it.
*/
WinpthreadsSpinlock *wSpinlock = InterlockedCompareExchangePointer (
(void **) pSpinlock, NULL, WINPTHREADS_SPINLOCK_INITIALIZER
);
if (unlikely (wSpinlock == NULL)) {
return EINVAL;
}
if (unlikely (wSpinlock == WINPTHREADS_SPINLOCK_INITIALIZER)) {
return 0;
}
switch (_pthread_wait_for_single_object (wSpinlock->Event, 0)) {
/**
* `wSpinlock->Event` was in signaled state, which means it was unlocked;
* we are holding the lock now which prevents other threads from locking it.
*/
case WAIT_OBJECT_0:
break;
/**
* `wSpinlock->Event` was in not-signaled state, which means some thread
* holds the lock.
*
* POSIX:
*
* If an implementation detects that the value specified by the lock argument
* to pthread_spin_destroy() or pthread_spin_init() refers to a locked spin
* lock object, or detects that the value specified by the lock argument to
* pthread_spin_init() refers to an already initialized spin lock object,
* it is recommended that the function should fail and report an [EBUSY] error.
*/
case WAIT_TIMEOUT:
return EBUSY;
default:
return EINVAL;
}
/**
* Invalidate `pthread_spinlock_t` object pointed by `lock`.
*/
InterlockedExchangePointer ((void **) pSpinlock, NULL);
CloseHandle (wSpinlock->Event);
free (wSpinlock);
return 0;
}
int pthread_spin_lock (pthread_spinlock_t *lock)
{
WinpthreadsSpinlock *wSpinlock = NULL;
int error_code = WinpthreadsSpinlockGet (lock, &wSpinlock);
if (error_code) {
return error_code;
}
DWORD threadId = GetCurrentThreadId ();
/**
* The pthread_spin_lock() function may fail if:
*
* [EDEADLK]
* A deadlock condition was detected.
*/
if (unlikely (wSpinlock->ThreadId == threadId)) {
return EDEADLK;
}
switch (_pthread_wait_for_single_object (wSpinlock->Event, INFINITE)) {
/**
* We are holding the lock now and `wSpinlock->Event` was reset to
* non-signaled state.
*/
case WAIT_OBJECT_0:
break;
default:
return EINVAL;
}
wSpinlock->ThreadId = threadId;
return 0;
}
int pthread_spin_trylock (pthread_spinlock_t *lock)
{
WinpthreadsSpinlock *wSpinlock = NULL;
int error_code = WinpthreadsSpinlockGet (lock, &wSpinlock);
if (error_code) {
return error_code;
}
/**
* Unlike `pthread_spin_lock`, no deadlock can occur even if calling thread
* owns the lock.
*/
switch (_pthread_wait_for_single_object (wSpinlock->Event, 0)) {
/**
* `wSpinlock->Event` was in signaled state, which means it was unlocked;
* we are holding the lock now and `wSpinlock->Event` was reset to
* non-signaled state.
*/
case WAIT_OBJECT_0:
break;
/**
* `wSpinlock->Event` was in non-signaled state, which means some thread
* holds the lock.
*
* The pthread_spin_trylock() function shall fail if:
*
* [EBUSY]
* A thread currently holds the lock.
*/
case WAIT_TIMEOUT:
return EBUSY;
default:
return EINVAL;
}
wSpinlock->ThreadId = GetCurrentThreadId ();
return 0;
}
int pthread_spin_unlock (pthread_spinlock_t *lock)
{
WinpthreadsSpinlock *wSpinlock = NULL;
int error_code = WinpthreadsSpinlockGet (lock, &wSpinlock);
if (error_code) {
return error_code;
}
DWORD threadId = GetCurrentThreadId ();
/**
* POSIX:
*
* If an implementation detects that the value specified by the lock argument
* to pthread_spin_unlock() refers to a spin lock object for which the
* current thread does not hold the lock, it is recommended that the function
* should fail and report an [EPERM] error.
*/
if (unlikely (wSpinlock->ThreadId != threadId)) {
return EPERM;
}
wSpinlock->ThreadId = THREAD_ID_NO_OWNER;
if (!SetEvent (wSpinlock->Event)) {
return EINVAL;
}
return 0;
}