/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "objfw-defs.h"
#include <errno.h>
#include "platform.h"
#if !defined(OF_HAVE_THREADS) || \
(!defined(OF_HAVE_PTHREADS) && !defined(OF_WINDOWS) && !defined(OF_AMIGAOS))
# error No mutexes available!
#endif
#import "macros.h"
/** @file */
#if defined(OF_HAVE_PTHREADS)
# include <pthread.h>
typedef pthread_mutex_t OFPlainMutex;
#elif defined(OF_WINDOWS)
# include <windows.h>
typedef CRITICAL_SECTION OFPlainMutex;
#elif defined(OF_AMIGAOS)
# include <exec/semaphores.h>
typedef struct SignalSemaphore OFPlainMutex;
#endif
#if defined(OF_HAVE_ATOMIC_OPS)
# import "OFAtomic.h"
typedef volatile int OFSpinlock;
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
typedef pthread_spinlock_t OFSpinlock;
#else
typedef OFPlainMutex OFSpinlock;
#endif
#ifdef OF_HAVE_SCHED_YIELD
# include <sched.h>
#endif
#if defined(OF_HAVE_RECURSIVE_PTHREAD_MUTEXES) || defined(OF_WINDOWS) || \
defined(OF_AMIGAOS)
# define OFPlainRecursiveMutex OFPlainMutex
#else
# import "OFTLSKey.h"
typedef struct {
OFPlainMutex mutex;
OFTLSKey count;
} OFPlainRecursiveMutex;
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Creates a new plain mutex.
*
* A plain mutex is similar to an @ref OFMutex, but does not use exceptions and
* is just a lightweight wrapper around the system's mutex implementation.
*
* @param mutex A pointer to the mutex to create
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainMutexNew(OFPlainMutex *mutex);
/**
* @brief Locks the specified mutex.
*
* @param mutex A pointer to the mutex to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainMutexLock(OFPlainMutex *mutex);
/**
* @brief Tries to lock the specified mutex without blocking.
*
* @param mutex A pointer to the mutex to try to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainMutexTryLock(OFPlainMutex *mutex);
/**
* @brief Unlocks the specified mutex.
*
* @param mutex A pointer to the mutex to unlock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainMutexUnlock(OFPlainMutex *mutex);
/**
* @brief Destroys the specified mutex
*
* @param mutex A pointer to the mutex to destruct
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainMutexFree(OFPlainMutex *mutex);
/**
* @brief Creates a new plain recursive mutex.
*
* A plain recursive mutex is similar to an @ref OFRecursiveMutex, but does not
* use exceptions and is just a lightweight wrapper around the system's
* recursive mutex implementation (or lacking that, a simple implementation of
* recursive mutexes via regular mutexes).
*
* @param rmutex A pointer to the recursive mutex to create
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainRecursiveMutexNew(OFPlainRecursiveMutex *rmutex);
/**
* @brief Locks the specified recursive mutex.
*
* @param rmutex A pointer to the recursive mutex to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainRecursiveMutexLock(OFPlainRecursiveMutex *rmutex);
/**
* @brief Tries to lock the specified recursive mutex without blocking.
*
* @param rmutex A pointer to the recursive mutex to try to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainRecursiveMutexTryLock(OFPlainRecursiveMutex *rmutex);
/**
* @brief Unlocks the specified recursive mutex.
*
* @param rmutex A pointer to the recursive mutex to unlock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainRecursiveMutexUnlock(OFPlainRecursiveMutex *rmutex);
/**
* @brief Destroys the specified recursive mutex
*
* @param rmutex A pointer to the recursive mutex to destruct
* @return 0 on success, or an error number from `<errno.h>` on error
*/
extern int OFPlainRecursiveMutexFree(OFPlainRecursiveMutex *rmutex);
#ifdef __cplusplus
}
#endif
/* Spinlocks are inlined for performance. */
/**
* @brief Yield the current thread, indicating to the OS that another thread
* should execute instead.
*/
static OF_INLINE void
OFYieldThread(void)
{
#if defined(OF_HAVE_SCHED_YIELD)
sched_yield();
#elif defined(OF_WINDOWS)
Sleep(0);
#endif
}
/**
* @brief Creates a new spinlock.
*
* @param spinlock A pointer to the spinlock to create
* @return 0 on success, or an error number from `<errno.h>` on error
*/
static OF_INLINE int
OFSpinlockNew(OFSpinlock *spinlock)
{
#if defined(OF_HAVE_ATOMIC_OPS)
*spinlock = 0;
return 0;
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
return pthread_spin_init(spinlock, 0);
#else
return OFPlainMutexNew(spinlock);
#endif
}
/**
* @brief Tries to lock a spinlock.
*
* @param spinlock A pointer to the spinlock to try to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
static OF_INLINE int
OFSpinlockTryLock(OFSpinlock *spinlock)
{
#if defined(OF_HAVE_ATOMIC_OPS)
if (OFAtomicIntCompareAndSwap(spinlock, 0, 1)) {
OFAcquireMemoryBarrier();
return 0;
}
return EBUSY;
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
return pthread_spin_trylock(spinlock);
#else
return OFPlainMutexTryLock(spinlock);
#endif
}
/**
* @brief Locks a spinlock.
*
* @param spinlock A pointer to the spinlock to lock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
static OF_INLINE int
OFSpinlockLock(OFSpinlock *spinlock)
{
#if defined(OF_HAVE_ATOMIC_OPS)
size_t i;
for (i = 0; i < 10; i++)
if (OFSpinlockTryLock(spinlock) == 0)
return 0;
while (OFSpinlockTryLock(spinlock) == EBUSY)
OFYieldThread();
return 0;
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
return pthread_spin_lock(spinlock);
#else
return OFPlainMutexLock(spinlock);
#endif
}
/**
* @brief Unlocks a spinlock.
*
* @param spinlock A pointer to the spinlock to unlock
* @return 0 on success, or an error number from `<errno.h>` on error
*/
static OF_INLINE int
OFSpinlockUnlock(OFSpinlock *spinlock)
{
#if defined(OF_HAVE_ATOMIC_OPS)
bool ret = OFAtomicIntCompareAndSwap(spinlock, 1, 0);
OFReleaseMemoryBarrier();
return (ret ? 0 : EINVAL);
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
return pthread_spin_unlock(spinlock);
#else
return OFPlainMutexUnlock(spinlock);
#endif
}
/**
* @brief Destroys a spinlock.
*
* @param spinlock A pointer to the spinlock to destroy
* @return 0 on success, or an error number from `<errno.h>` on error
*/
static OF_INLINE int
OFSpinlockFree(OFSpinlock *spinlock)
{
#if defined(OF_HAVE_ATOMIC_OPS)
(void)spinlock;
return 0;
#elif defined(OF_HAVE_PTHREAD_SPINLOCKS)
return pthread_spin_destroy(spinlock);
#else
return OFPlainMutexFree(spinlock);
#endif
}