From 37c59b003cd09e498fe85b99ec82fc0eadb343c0 Mon Sep 17 00:00:00 2001 From: MechSlayer <0jcrespo1996@gmail.com> Date: Fri, 5 Sep 2025 15:16:06 +0200 Subject: [PATCH] feature: add synchronization primitives api including mutexes, condition variables, and read-write locks --- Source/DrangPlatform/Include/drang/sync.h | 419 ++++++++++++++++++++++ 1 file changed, 419 insertions(+) create mode 100644 Source/DrangPlatform/Include/drang/sync.h diff --git a/Source/DrangPlatform/Include/drang/sync.h b/Source/DrangPlatform/Include/drang/sync.h new file mode 100644 index 0000000..bc44602 --- /dev/null +++ b/Source/DrangPlatform/Include/drang/sync.h @@ -0,0 +1,419 @@ +#pragma once +#include "error.h" +#include "platform.h" +#include +#include + +DRANG_BEGIN_DECLS + +/** + * @brief Opaque structure representing a mutex (mutual exclusion lock). + * + * A mutex provides mutual exclusion synchronization, allowing only one thread + * to access a shared resource at a time. This is an opaque structure whose + * actual implementation depends on the target platform. + * + * @remarks Use drang_mutex_new() or drang_mutex_init() to create and initialize. + * Always pair with drang_mutex_free() or drang_mutex_fini() respectively. + */ +struct drang_mutex; + +/** + * @brief Returns the size in bytes required for a mutex structure. + * + * This function returns the platform-specific size needed to allocate + * a mutex structure. Useful for stack allocation or custom memory management. + * + * @return Size in bytes required for a drang_mutex structure. + * @remarks The returned size is platform-specific and may vary between builds. + */ +DRANG_API size_t drang_mutex_size(void); + +/** + * @brief Creates and initializes a new mutex on the heap. + * + * Allocates memory for a new mutex and initializes it to a usable state. + * The mutex is created in an unlocked state. + * + * @param[out] mutex Pointer to store the address of the newly created mutex. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - mutex created and initialized. + * @retval DRANG_ENOMEM Insufficient memory to allocate the mutex. + * @retval DRANG_EINVAL Invalid parameter (mutex is NULL). + * @remarks The created mutex must be freed with drang_mutex_free(). + * The mutex is created in an unlocked state and ready for use. + */ +DRANG_API int drang_mutex_new(struct drang_mutex **mutex); + +/** + * @brief Destroys and frees a mutex created with drang_mutex_new(). + * + * Finalizes the mutex and releases its allocated memory. The mutex should + * not be locked when this function is called. + * + * @param[in] mutex Pointer to the mutex to destroy and free, or NULL. + * @remarks Safe to call with NULL pointer. The mutex should be unlocked + * before calling this function. Undefined behavior if the mutex + * is currently locked or if other threads are waiting on it. + */ +DRANG_API void drang_mutex_free(struct drang_mutex *mutex); + +/** + * @brief Initializes a pre-allocated mutex structure. + * + * Initializes a mutex structure that was allocated by the caller (e.g., on + * the stack or as part of a larger structure). The mutex is initialized + * to an unlocked state. + * + * @param[in] mutex Pointer to the mutex structure to initialize. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - mutex initialized and ready for use. + * @retval DRANG_EINVAL Invalid parameter (mutex is NULL). + * @retval DRANG_EBUSY Mutex is already initialized. + * @remarks The mutex must be finalized with drang_mutex_fini() when no longer needed. + * Use this for stack-allocated mutexes or when embedding in other structures. + */ +DRANG_API int drang_mutex_init(struct drang_mutex *mutex); + +/** + * @brief Finalizes a mutex initialized with drang_mutex_init(). + * + * Cleans up resources associated with the mutex. Does not free the mutex + * structure itself. The mutex should not be locked when this function is called. + * + * @param[in] mutex Pointer to the mutex to finalize. + * @remarks The mutex should be unlocked before calling this function. + * Undefined behavior if the mutex is currently locked or if other + * threads are waiting on it. Safe to call with NULL pointer. + */ +DRANG_API void drang_mutex_fini(struct drang_mutex *mutex); + +/** + * @brief Locks the mutex, blocking until acquisition is possible. + * + * Attempts to lock the mutex. If the mutex is already locked by another thread, + * the calling thread will block until the mutex becomes available. If the same + * thread attempts to lock a mutex it already owns, the behavior is undefined. + * + * @param[in] mutex Pointer to the mutex to lock. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - mutex is now locked by the calling thread. + * @retval DRANG_EINVAL Invalid parameter (mutex is NULL or not initialized). + * @retval DRANG_EDEADLK Deadlock detected (implementation-specific). + * @remarks The mutex must be unlocked with drang_mutex_unlock() by the same thread. + * Recursive locking (same thread locking twice) results in undefined behavior. + */ +DRANG_API int drang_mutex_lock(struct drang_mutex *mutex); + +/** + * @brief Unlocks a previously locked mutex. + * + * Releases the mutex, allowing other threads to acquire it. The mutex must + * have been previously locked by the calling thread. + * + * @param[in] mutex Pointer to the mutex to unlock. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - mutex is now unlocked and available to other threads. + * @retval DRANG_EINVAL Invalid parameter (mutex is NULL or not initialized). + * @retval DRANG_EPERM The calling thread does not own the mutex. + * @remarks Only the thread that locked the mutex should unlock it. + * Unlocking a mutex that is not locked results in undefined behavior. + */ +DRANG_API int drang_mutex_unlock(struct drang_mutex *mutex); + +/** + * @brief Opaque structure representing a condition variable. + * + * A condition variable allows threads to wait for certain conditions to become true. + * It works in conjunction with a mutex to provide efficient thread synchronization. + * This is an opaque structure whose actual implementation depends on the target platform. + * + * @remarks Always use condition variables with an associated mutex. + * Use drang_cond_new() or drang_cond_init() to create and initialize. + * Always pair with drang_cond_free() or drang_cond_fini() respectively. + */ +struct drang_cond; + +/** + * @brief Returns the size in bytes required for a condition variable structure. + * + * This function returns the platform-specific size needed to allocate + * a condition variable structure. Useful for stack allocation or custom memory management. + * + * @return Size in bytes required for a drang_cond structure. + * @remarks The returned size is platform-specific and may vary between builds. + */ +DRANG_API size_t drang_cond_size(void); + +/** + * @brief Creates and initializes a new condition variable on the heap. + * + * Allocates memory for a new condition variable and initializes it to a usable state. + * The condition variable is ready to be used with wait, signal, and broadcast operations. + * + * @param[out] cond Pointer to store the address of the newly created condition variable. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - condition variable created and initialized. + * @retval DRANG_ENOMEM Insufficient memory to allocate the condition variable. + * @retval DRANG_EINVAL Invalid parameter (cond is NULL). + * @remarks The created condition variable must be freed with drang_cond_free(). + * Ready for use immediately after successful creation. + */ +DRANG_API int drang_cond_new(struct drang_cond **cond); + +/** + * @brief Destroys and frees a condition variable created with drang_cond_new(). + * + * Finalizes the condition variable and releases its allocated memory. No threads + * should be waiting on the condition variable when this function is called. + * + * @param[in] cond Pointer to the condition variable to destroy and free, or NULL. + * @remarks Safe to call with NULL pointer. Undefined behavior if threads are + * currently waiting on this condition variable. + */ +DRANG_API void drang_cond_free(struct drang_cond *cond); + +/** + * @brief Initializes a pre-allocated condition variable structure. + * + * Initializes a condition variable structure that was allocated by the caller + * (e.g., on the stack or as part of a larger structure). The condition variable + * is initialized and ready for use. + * + * @param[in] cond Pointer to the condition variable structure to initialize. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - condition variable initialized and ready for use. + * @retval DRANG_EINVAL Invalid parameter (cond is NULL). + * @retval DRANG_EBUSY Condition variable is already initialized. + * @remarks The condition variable must be finalized with drang_cond_fini() when no longer needed. + * Use this for stack-allocated condition variables or when embedding in other structures. + */ +DRANG_API int drang_cond_init(struct drang_cond *cond); + +/** + * @brief Finalizes a condition variable initialized with drang_cond_init(). + * + * Cleans up resources associated with the condition variable. Does not free the + * condition variable structure itself. No threads should be waiting on the + * condition variable when this function is called. + * + * @param[in] cond Pointer to the condition variable to finalize. + * @remarks Undefined behavior if threads are currently waiting on this condition variable. + * Safe to call with NULL pointer. + */ +DRANG_API void drang_cond_fini(struct drang_cond *cond); + +/** + * @brief Signals one thread waiting on the condition variable. + * + * Wakes up at least one thread that is currently waiting on the condition variable. + * If no threads are waiting, the signal is lost (no queuing). If multiple threads + * are waiting, it is unspecified which thread will be awakened. + * + * @param[in] cond Pointer to the condition variable to signal. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - signal sent (though no guarantee a thread was waiting). + * @retval DRANG_EINVAL Invalid parameter (cond is NULL or not initialized). + * @remarks Does not require holding the associated mutex, but it's recommended + * for predictable behavior. If no threads are waiting, the signal is lost. + */ +DRANG_API int drang_cond_signal(struct drang_cond *cond); + +/** + * @brief Signals all threads waiting on the condition variable. + * + * Wakes up all threads that are currently waiting on the condition variable. + * If no threads are waiting, the broadcast is lost (no queuing). All waiting + * threads will compete to reacquire their associated mutex. + * + * @param[in] cond Pointer to the condition variable to broadcast. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - broadcast sent (though no guarantee threads were waiting). + * @retval DRANG_EINVAL Invalid parameter (cond is NULL or not initialized). + * @remarks Does not require holding the associated mutex, but it's recommended + * for predictable behavior. If no threads are waiting, the broadcast is lost. + */ +DRANG_API int drang_cond_broadcast(struct drang_cond *cond); + +/** + * @brief Waits indefinitely on a condition variable. + * + * Atomically unlocks the mutex and waits for the condition variable to be signaled. + * When awakened (by signal or broadcast), the mutex is reacquired before returning. + * The calling thread must own the mutex before calling this function. + * + * @param[in] cond Pointer to the condition variable to wait on. + * @param[in] mutex Pointer to the mutex associated with the condition variable. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - condition was signaled and mutex reacquired. + * @retval DRANG_EINVAL Invalid parameter (cond or mutex is NULL or not initialized). + * @retval DRANG_EPERM The calling thread does not own the mutex. + * @remarks The mutex must be locked by the calling thread before this call. + * Spurious wakeups are possible - always check your condition in a loop. + * The mutex is guaranteed to be locked when this function returns successfully. + */ +DRANG_API int drang_cond_wait(struct drang_cond *cond, struct drang_mutex *mutex); + +/** + * @brief Waits on a condition variable with a timeout. + * + * Atomically unlocks the mutex and waits for the condition variable to be signaled + * or for the timeout to expire. When awakened or timed out, the mutex is reacquired + * before returning. The calling thread must own the mutex before calling this function. + * + * @param[in] cond Pointer to the condition variable to wait on. + * @param[in] mutex Pointer to the mutex associated with the condition variable. + * @param[in] timeout_ms Timeout in milliseconds (0 = no timeout, wait indefinitely). + * @return 0 on success, negative error code on failure or timeout. + * @retval 0 Success - condition was signaled and mutex reacquired. + * @retval DRANG_ETIMEDOUT Timeout expired before condition was signaled. + * @retval DRANG_EINVAL Invalid parameter (cond or mutex is NULL or not initialized). + * @retval DRANG_EPERM The calling thread does not own the mutex. + * @remarks The mutex must be locked by the calling thread before this call. + * Spurious wakeups are possible - always check your condition in a loop. + * The mutex is guaranteed to be locked when this function returns (success or timeout). + * A timeout of 0 means wait indefinitely (equivalent to drang_cond_wait). + */ +DRANG_API int drang_cond_timedwait(struct drang_cond *cond, struct drang_mutex *mutex, uint64_t timeout_ms); + +/** + * @brief Opaque structure representing a read-write lock. + * + * A read-write lock allows multiple concurrent readers or a single writer. + * This provides better performance than a mutex when reads are more frequent + * than writes. This is an opaque structure whose actual implementation depends + * on the target platform. + * + * @remarks Readers can acquire the lock concurrently, but writers have exclusive access. + * Use drang_rwlock_new() or drang_rwlock_init() to create and initialize. + * Always pair with drang_rwlock_free() or drang_rwlock_fini() respectively. + */ +struct drang_rwlock; + +/** + * @brief Returns the size in bytes required for a read-write lock structure. + * + * This function returns the platform-specific size needed to allocate + * a read-write lock structure. Useful for stack allocation or custom memory management. + * + * @return Size in bytes required for a drang_rwlock structure. + * @remarks The returned size is platform-specific and may vary between builds. + */ +DRANG_API size_t drang_rwlock_size(void); + +/** + * @brief Creates and initializes a new read-write lock on the heap. + * + * Allocates memory for a new read-write lock and initializes it to a usable state. + * The lock is created in an unlocked state, ready for readers or writers. + * + * @param[out] rwlock Pointer to store the address of the newly created read-write lock. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - read-write lock created and initialized. + * @retval DRANG_ENOMEM Insufficient memory to allocate the read-write lock. + * @retval DRANG_EINVAL Invalid parameter (rwlock is NULL). + * @remarks The created read-write lock must be freed with drang_rwlock_free(). + * The lock is created in an unlocked state and ready for use. + */ +DRANG_API int drang_rwlock_new(struct drang_rwlock **rwlock); + +/** + * @brief Destroys and frees a read-write lock created with drang_rwlock_new(). + * + * Finalizes the read-write lock and releases its allocated memory. The lock should + * not have any readers or writers when this function is called. + * + * @param[in] rwlock Pointer to the read-write lock to destroy and free, or NULL. + * @remarks Safe to call with NULL pointer. The lock should be unlocked + * before calling this function. Undefined behavior if the lock + * currently has readers or a writer. + */ +DRANG_API void drang_rwlock_free(struct drang_rwlock *rwlock); + +/** + * @brief Initializes a pre-allocated read-write lock structure. + * + * Initializes a read-write lock structure that was allocated by the caller + * (e.g., on the stack or as part of a larger structure). The lock is + * initialized to an unlocked state. + * + * @param[in] rwlock Pointer to the read-write lock structure to initialize. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - read-write lock initialized and ready for use. + * @retval DRANG_EINVAL Invalid parameter (rwlock is NULL). + * @retval DRANG_EBUSY Read-write lock is already initialized. + * @remarks The lock must be finalized with drang_rwlock_fini() when no longer needed. + * Use this for stack-allocated locks or when embedding in other structures. + */ +DRANG_API int drang_rwlock_init(struct drang_rwlock *rwlock); + +/** + * @brief Finalizes a read-write lock initialized with drang_rwlock_init(). + * + * Cleans up resources associated with the read-write lock. Does not free the + * lock structure itself. The lock should not have any readers or writers + * when this function is called. + * + * @param[in] rwlock Pointer to the read-write lock to finalize. + * @remarks The lock should be unlocked before calling this function. + * Undefined behavior if the lock currently has readers or a writer. + * Safe to call with NULL pointer. + */ +DRANG_API void drang_rwlock_fini(struct drang_rwlock *rwlock); + +/** + * @brief Acquires a read lock on the read-write lock. + * + * Attempts to acquire a read lock. Multiple threads can hold read locks + * simultaneously, but read locks are exclusive with write locks. If a writer + * currently holds the lock, the calling thread will block until the write + * lock is released. + * + * @param[in] rwlock Pointer to the read-write lock to acquire for reading. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - read lock acquired. + * @retval DRANG_EINVAL Invalid parameter (rwlock is NULL or not initialized). + * @retval DRANG_EDEADLK Deadlock detected (implementation-specific). + * @remarks The read lock must be released with drang_rwlock_unlock(). + * Multiple threads can hold read locks concurrently. + * Read locks block writers but not other readers. + */ +DRANG_API int drang_rwlock_rdlock(struct drang_rwlock *rwlock); + +/** + * @brief Acquires a write lock on the read-write lock. + * + * Attempts to acquire a write lock. Write locks are exclusive - only one thread + * can hold a write lock, and write locks are exclusive with read locks. If any + * readers or another writer currently hold the lock, the calling thread will + * block until all locks are released. + * + * @param[in] rwlock Pointer to the read-write lock to acquire for writing. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - write lock acquired. + * @retval DRANG_EINVAL Invalid parameter (rwlock is NULL or not initialized). + * @retval DRANG_EDEADLK Deadlock detected (implementation-specific). + * @remarks The write lock must be released with drang_rwlock_unlock(). + * Write locks are exclusive - only one writer at a time. + * Write locks block both readers and other writers. + */ +DRANG_API int drang_rwlock_wrlock(struct drang_rwlock *rwlock); + +/** + * @brief Releases a previously acquired read or write lock. + * + * Releases either a read lock or write lock that was previously acquired by + * the calling thread. The type of lock (read or write) is tracked internally. + * + * @param[in] rwlock Pointer to the read-write lock to unlock. + * @return 0 on success, negative error code on failure. + * @retval 0 Success - lock released. + * @retval DRANG_EINVAL Invalid parameter (rwlock is NULL or not initialized). + * @retval DRANG_EPERM The calling thread does not hold any lock on this rwlock. + * @remarks Only the thread that acquired the lock should release it. + * Works for both read and write locks. + * Unlocking when no lock is held results in undefined behavior. + */ +DRANG_API int drang_rwlock_unlock(struct drang_rwlock *rwlock); + +DRANG_END_DECLS