[vlc-commits] threads: condition variables with futex
Rémi Denis-Courmont
git at videolan.org
Fri May 27 23:43:03 CEST 2016
vlc | branch: master | Rémi Denis-Courmont <remi at remlab.net> | Mon Feb 8 20:31:46 2016 +0200| [716ff3575a528cf077df31ef0220974d528a344b] | committer: Rémi Denis-Courmont
threads: condition variables with futex
This hopefully deals with both broadcast and cancellation correctly.
> http://git.videolan.org/gitweb.cgi/vlc.git/?a=commit;h=716ff3575a528cf077df31ef0220974d528a344b
---
include/vlc_threads.h | 31 ++++++++++--
src/misc/threads.c | 130 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 155 insertions(+), 6 deletions(-)
diff --git a/include/vlc_threads.h b/include/vlc_threads.h
index 5733aeb..181f863 100644
--- a/include/vlc_threads.h
+++ b/include/vlc_threads.h
@@ -328,6 +328,14 @@ typedef struct vlc_timer *vlc_timer_t;
#endif
+#ifdef LIBVLC_NEED_CONDVAR
+typedef struct
+{
+ int value;
+} vlc_cond_t;
+# define VLC_STATIC_COND { 0 }
+#endif
+
#ifdef LIBVLC_NEED_SEMAPHORE
typedef struct vlc_sem
{
@@ -918,6 +926,14 @@ VLC_API unsigned vlc_timer_getoverrun(vlc_timer_t) VLC_USED;
*/
VLC_API unsigned vlc_GetCPUCount(void);
+enum
+{
+ VLC_CLEANUP_PUSH,
+ VLC_CLEANUP_POP,
+ VLC_CANCEL_ADDR_SET,
+ VLC_CANCEL_ADDR_CLEAR,
+};
+
#if defined (LIBVLC_USE_PTHREAD_CLEANUP)
/**
* Registers a thread cancellation handler.
@@ -947,11 +963,6 @@ VLC_API unsigned vlc_GetCPUCount(void);
# define vlc_cleanup_pop( ) pthread_cleanup_pop (0)
#else
-enum
-{
- VLC_CLEANUP_PUSH,
- VLC_CLEANUP_POP,
-};
typedef struct vlc_cleanup_t vlc_cleanup_t;
struct vlc_cleanup_t
@@ -981,6 +992,16 @@ static inline void vlc_cleanup_lock (void *lock)
}
#define mutex_cleanup_push( lock ) vlc_cleanup_push (vlc_cleanup_lock, lock)
+static inline void vlc_cancel_addr_set(void *addr)
+{
+ vlc_control_cancel(VLC_CANCEL_ADDR_SET, addr);
+}
+
+static inline void vlc_cancel_addr_clear(void *addr)
+{
+ vlc_control_cancel(VLC_CANCEL_ADDR_CLEAR, addr);
+}
+
#ifdef __cplusplus
/**
* Helper C++ class to lock a mutex.
diff --git a/src/misc/threads.c b/src/misc/threads.c
index 1972621..2f9be7e 100644
--- a/src/misc/threads.c
+++ b/src/misc/threads.c
@@ -1,7 +1,7 @@
/*****************************************************************************
* threads.c: LibVLC generic thread support
*****************************************************************************
- * Copyright (C) 2009-2012 Rémi Denis-Courmont
+ * Copyright (C) 2009-2016 Rémi Denis-Courmont
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
@@ -23,6 +23,7 @@
#endif
#include <assert.h>
+#include <errno.h>
#include <vlc_common.h>
@@ -48,6 +49,133 @@ void vlc_global_mutex (unsigned n, bool acquire)
vlc_mutex_unlock (lock);
}
+#ifdef LIBVLC_NEED_CONDVAR
+#include <stdalign.h>
+#include <vlc_atomic.h>
+
+static void vlc_cancel_addr_prepare(void *addr)
+{
+ /* Let thread subsystem on address to broadcast for cancellation */
+ vlc_cancel_addr_set(addr);
+ vlc_cleanup_push(vlc_cancel_addr_clear, addr);
+ /* Check if cancellation was pending before vlc_cancel_addr_set() */
+ vlc_testcancel();
+ vlc_cleanup_pop();
+}
+
+static void vlc_cancel_addr_finish(void *addr)
+{
+ vlc_cancel_addr_clear(addr);
+ /* Act on cancellation as potential wake-up source */
+ vlc_testcancel();
+}
+
+static inline atomic_int *vlc_cond_value(vlc_cond_t *cond)
+{
+ /* XXX: ugly but avoids including vlc_atomic.h in vlc_threads.h */
+ static_assert (sizeof (cond->value) <= sizeof (atomic_int),
+ "Size mismatch!");
+ static_assert ((alignof (cond->value) % alignof (atomic_int)) == 0,
+ "Alignment mismatch");
+ return (atomic_int *)&cond->value;
+}
+
+void vlc_cond_init(vlc_cond_t *cond)
+{
+ /* Initial value is irrelevant but set it for happy debuggers */
+ atomic_init(vlc_cond_value(cond), 0);
+}
+
+void vlc_cond_init_daytime(vlc_cond_t *cond)
+{
+ vlc_cond_init(cond);
+}
+
+void vlc_cond_destroy(vlc_cond_t *cond)
+{
+ /* Tempting sanity check but actually incorrect:
+ assert((atomic_load_explicit(vlc_cond_value(cond),
+ memory_order_relaxed) & 1) == 0);
+ * Due to timeouts and spurious wake-ups, the futex value can look like
+ * there are waiters, even though there are none. */
+ (void) cond;
+}
+
+void vlc_cond_signal(vlc_cond_t *cond)
+{
+ /* Probably the best documented approach is that of Bionic: increment
+ * the futex here, and simply load the value in cond_wait(). This has a bug
+ * as unlikely as well-known: signals get lost if the futex is incremented
+ * an exact multiple of 2^(CHAR_BIT * sizeof (int)) times.
+ *
+ * Here, we simply clear the low order bit, while cond_wait() sets it.
+ * It makes cond_wait() somewhat slower, but it should be free of bugs,
+ * leaves the other bits free for other uses (e.g. flags).
+ **/
+ atomic_fetch_and_explicit(vlc_cond_value(cond), -2, memory_order_relaxed);
+ /* We have to wake the futex even if the low order bit is cleared, as there
+ * could be more than one thread queued, not all of them already awoken. */
+ vlc_addr_signal(&cond->value);
+}
+
+void vlc_cond_broadcast(vlc_cond_t *cond)
+{
+ atomic_fetch_and_explicit(vlc_cond_value(cond), -2, memory_order_relaxed);
+ vlc_addr_broadcast(&cond->value);
+}
+
+void vlc_cond_wait(vlc_cond_t *cond, vlc_mutex_t *mutex)
+{
+ int value = atomic_fetch_or_explicit(vlc_cond_value(cond), 1,
+ memory_order_relaxed) | 1;
+
+ vlc_cancel_addr_prepare(&cond->value);
+ vlc_mutex_unlock(mutex);
+
+ vlc_addr_wait(&cond->value, value);
+
+ vlc_mutex_lock(mutex);
+ vlc_cancel_addr_finish(&cond->value);
+}
+
+static int vlc_cond_wait_delay(vlc_cond_t *cond, vlc_mutex_t *mutex,
+ mtime_t delay)
+{
+ int value = atomic_fetch_or_explicit(vlc_cond_value(cond), 1,
+ memory_order_relaxed) | 1;
+
+ vlc_cancel_addr_prepare(&cond->value);
+ vlc_mutex_unlock(mutex);
+
+ if (delay > 0)
+ value = vlc_addr_timedwait(&cond->value, value, delay);
+ else
+ value = 0;
+
+ vlc_mutex_lock(mutex);
+ vlc_cancel_addr_finish(&cond->value);
+
+ return value ? 0 : ETIMEDOUT;
+}
+
+int vlc_cond_timedwait(vlc_cond_t *cond, vlc_mutex_t *mutex, mtime_t deadline)
+{
+ return vlc_cond_wait_delay(cond, mutex, deadline - mdate());
+}
+
+int vlc_cond_timedwait_daytime(vlc_cond_t *cond, vlc_mutex_t *mutex,
+ time_t deadline)
+{
+ struct timespec ts;
+
+ timespec_get(&ts, TIME_UTC);
+ deadline -= ts.tv_sec * CLOCK_FREQ;
+ deadline -= ts.tv_nsec / (1000000000 / CLOCK_FREQ);
+
+ return vlc_cond_wait_delay(cond, mutex, deadline);
+}
+#endif
+
#ifdef LIBVLC_NEED_RWLOCK
/*** Generic read/write locks ***/
#include <stdlib.h>
More information about the vlc-commits
mailing list