From 0a8a53335b5770cd1acaa612b495bdbc41a8d1ee Mon Sep 17 00:00:00 2001 From: =?utf8?q?R=C3=A9mi=20Denis-Courmont?= Date: Wed, 19 Feb 2020 21:09:06 +0200 Subject: [PATCH] thread: add generic futex-based muteces This provides a common implementation of fast (non-debug), error-checking (debug) and recursive muteces on top of vlc_atomic_wait() and vlc_atomic_notify_one(), using Drepper's tristate mutex algorithm. Benefits of this implementation include: - Error checking is supported on all platforms (including Windows). - Initialization can never fail, is not subject to aborts. - Destruction is a no-op and can be removed altogether. - Static muteces do not leak or need kludges. - Ownership can be checked directly without the mutex mark system. - Non-ownership can be checked in assertion. Because the player code uses the same vlc_mutex_t typedef for both non-recursive and recursive usages, disentanglement is not possible. This patchset thus supports both semantics together as before. --- include/vlc_threads.h | 55 ++++++++++++++----- src/misc/threads.c | 124 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 13 deletions(-) diff --git a/include/vlc_threads.h b/include/vlc_threads.h index 836f042891..598e6fb203 100644 --- a/include/vlc_threads.h +++ b/include/vlc_threads.h @@ -77,6 +77,7 @@ typedef struct }; } vlc_mutex_t; #define VLC_STATIC_MUTEX { false, { { false, 0 } } } +#define LIBVLC_DONT_WANT_MUTEX #define LIBVLC_NEED_RWLOCK typedef INIT_ONCE vlc_once_t; #define VLC_STATIC_ONCE INIT_ONCE_STATIC_INIT @@ -125,6 +126,7 @@ typedef struct }; } vlc_mutex_t; #define VLC_STATIC_MUTEX { false, { { false, 0 } } } +#define LIBVLC_DONT_WANT_MUTEX #define LIBVLC_NEED_RWLOCK typedef struct { @@ -180,6 +182,7 @@ typedef pthread_t vlc_osthread_t; #define vlc_thread_equal(a,b) pthread_equal(a,b) typedef pthread_mutex_t vlc_mutex_t; #define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER +#define LIBVLC_DONT_WANT_MUTEX typedef pthread_once_t vlc_once_t; #define VLC_STATIC_ONCE PTHREAD_ONCE_INIT typedef pthread_key_t vlc_threadvar_t; @@ -227,6 +230,7 @@ typedef pthread_t vlc_osthread_t; #define vlc_thread_equal(a,b) pthread_equal(a,b) typedef pthread_mutex_t vlc_mutex_t; #define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER +#define LIBVLC_DONT_WANT_MUTEX typedef pthread_rwlock_t vlc_rwlock_t; #define VLC_STATIC_RWLOCK PTHREAD_RWLOCK_INITIALIZER typedef pthread_once_t vlc_once_t; @@ -271,21 +275,9 @@ typedef struct typedef pthread_t vlc_osthread_t; #define vlc_thread_equal(a,b) pthread_equal(a,b) -/** - * Mutex. - * - * Storage space for a mutual exclusion lock. - * - * \ingroup mutex - */ typedef pthread_mutex_t vlc_mutex_t; - -/** - * Static initializer for (static) mutex. - * - * \ingroup mutex - */ #define VLC_STATIC_MUTEX PTHREAD_MUTEX_INITIALIZER +#define LIBVLC_DONT_WANT_MUTEX /** * Read/write lock. @@ -343,6 +335,43 @@ typedef struct vlc_timer *vlc_timer_t; * \defgroup mutex Mutual exclusion locks * @{ */ +#ifndef LIBVLC_DONT_WANT_MUTEX +/** + * Mutex. + * + * Storage space for a mutual exclusion lock. + */ +typedef struct +{ + union { +#ifndef __cplusplus + struct { + atomic_uint value; + atomic_uint recursion; + const void *_Atomic owner; + }; +#endif + struct { + unsigned int value; + unsigned int recursion; + const void *owner; + } dummy; + }; +} vlc_mutex_t; + +/** + * Static initializer for (static) mutex. + * + * \note This only works in C code. + * In C++, consider using a global \ref vlc::threads::mutex instance instead. + */ +#define VLC_STATIC_MUTEX { \ + .value = ATOMIC_VAR_INIT(0), \ + .recursion = ATOMIC_VAR_INIT(0), \ + .owner = ATOMIC_VAR_INIT(NULL), \ +} +#endif + /** * Initializes a fast mutex. * diff --git a/src/misc/threads.c b/src/misc/threads.c index bdb3c9d687..357687407d 100644 --- a/src/misc/threads.c +++ b/src/misc/threads.c @@ -201,6 +201,130 @@ void (vlc_tick_sleep)(vlc_tick_t delay) } #endif +#ifndef LIBVLC_DONT_WANT_MUTEX +static void vlc_mutex_init_common(vlc_mutex_t *mtx, bool recursive) +{ + atomic_init(&mtx->value, 0); + atomic_init(&mtx->recursion, recursive); + atomic_init(&mtx->owner, NULL); +} + +void vlc_mutex_init(vlc_mutex_t *mtx) +{ + vlc_mutex_init_common(mtx, false); +} + +void vlc_mutex_init_recursive(vlc_mutex_t *mtx) +{ + vlc_mutex_init_common(mtx, true); +} + +void vlc_mutex_destroy(vlc_mutex_t *mtx) +{ + assert(atomic_load_explicit(&mtx->value, memory_order_relaxed) == 0); + assert(atomic_load_explicit(&mtx->recursion, memory_order_relaxed) <= 1); + assert(atomic_load_explicit(&mtx->owner, memory_order_relaxed) == NULL); + (void) mtx; +} + +static _Thread_local char thread_self[1]; +#define THREAD_SELF ((const void *)thread_self) + +static bool vlc_mutex_held(const vlc_mutex_t *mtx) +{ + /* This comparison is thread-safe: + * Even though other threads may modify the owner field at any time, + * they will never make ti compare equal to the calling thread. + */ + return THREAD_SELF == atomic_load_explicit(&mtx->owner, + memory_order_relaxed); +} + +void vlc_mutex_lock(vlc_mutex_t *mtx) +{ + unsigned value; + + /* This is the Drepper (non-recursive) mutex algorithm + * from his "Futexes are tricky" paper. The mutex can value be: + * - 0: the mutex is free + * - 1: the mutex is locked and uncontended + * - 2: the mutex is contended (i.e., unlock needs to wake up a waiter) + */ + if (vlc_mutex_trylock(mtx) == 0) + return; + + int canc = vlc_savecancel(); /* locking is never a cancellation point */ + + while ((value = atomic_exchange_explicit(&mtx->value, 2, + memory_order_acquire)) != 0) + vlc_atomic_wait(&mtx->value, 2); + + vlc_restorecancel(canc); + atomic_store_explicit(&mtx->owner, THREAD_SELF, memory_order_relaxed); + vlc_mutex_mark(mtx); +} + +int vlc_mutex_trylock(vlc_mutex_t *mtx) +{ + /* Check the recursion counter: + * - 0: mutex is not recursive. + * - 1: mutex is recursive but free or locked non-recursively. + * - n > 1: mutex is recursive and locked n time(s). + */ + unsigned recursion = atomic_load_explicit(&mtx->recursion, + memory_order_relaxed); + if (unlikely(recursion) && vlc_mutex_held(mtx)) { + /* This thread already owns the mutex, locks recursively. + * Other threads shall not have modified the recursion or owner fields. + */ + atomic_store_explicit(&mtx->recursion, recursion + 1, + memory_order_relaxed); + vlc_mutex_mark(mtx); + return 0; + } else + assert(!vlc_mutex_held(mtx)); + + unsigned value = 0; + + if (atomic_compare_exchange_strong_explicit(&mtx->value, &value, 1, + memory_order_acquire, + memory_order_relaxed)) { + atomic_store_explicit(&mtx->owner, THREAD_SELF, memory_order_relaxed); + vlc_mutex_mark(mtx); + return 0; + } + + return EBUSY; +} + +void vlc_mutex_unlock(vlc_mutex_t *mtx) +{ + assert(vlc_mutex_held(mtx)); + + unsigned recursion = atomic_load_explicit(&mtx->recursion, + memory_order_relaxed); + if (unlikely(recursion > 1)) { + /* Non-last recursive unlocking. */ + atomic_store_explicit(&mtx->recursion, recursion - 1, + memory_order_relaxed); + vlc_mutex_unmark(mtx); + return; + } + + atomic_store_explicit(&mtx->owner, NULL, memory_order_relaxed); + vlc_mutex_unmark(mtx); + + switch (atomic_exchange_explicit(&mtx->value, 0, memory_order_release)) { + case 2: + vlc_atomic_notify_one(&mtx->value); + case 1: + break; + default: + vlc_assert_unreachable(); + } +} +#endif + void vlc_cond_init(vlc_cond_t *cond) { cond->head = NULL; -- 2.20.1