| /* | |
| 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; | |
| } |