summaryrefslogtreecommitdiffstats
path: root/lib/libthr/thread/thr_once.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libthr/thread/thr_once.c')
-rw-r--r--lib/libthr/thread/thr_once.c65
1 files changed, 55 insertions, 10 deletions
diff --git a/lib/libthr/thread/thr_once.c b/lib/libthr/thread/thr_once.c
index cef478d..8716e75 100644
--- a/lib/libthr/thread/thr_once.c
+++ b/lib/libthr/thread/thr_once.c
@@ -31,23 +31,68 @@
*
* $FreeBSD$
*/
+
+#include "namespace.h"
#include <pthread.h>
+#include "un-namespace.h"
+
#include "thr_private.h"
__weak_reference(_pthread_once, pthread_once);
+#define ONCE_NEVER_DONE PTHREAD_NEEDS_INIT
+#define ONCE_DONE PTHREAD_DONE_INIT
+#define ONCE_IN_PROGRESS 0x02
+#define ONCE_MASK 0x03
+
+static pthread_mutex_t once_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t once_cv = PTHREAD_COND_INITIALIZER;
+
+/*
+ * POSIX:
+ * The pthread_once() function is not a cancellation point. However,
+ * if init_routine is a cancellation point and is canceled, the effect
+ * on once_control shall be as if pthread_once() was never called.
+ */
+
+static void
+once_cancel_handler(void *arg)
+{
+ pthread_once_t *once_control = arg;
+
+ _pthread_mutex_lock(&once_lock);
+ once_control->state = ONCE_NEVER_DONE;
+ _pthread_mutex_unlock(&once_lock);
+ _pthread_cond_broadcast(&once_cv);
+}
+
int
-_pthread_once(pthread_once_t * once_control, void (*init_routine) (void))
+_pthread_once(pthread_once_t *once_control, void (*init_routine) (void))
{
- if (once_control->state == PTHREAD_NEEDS_INIT) {
- if (_thread_initial == NULL)
- _thread_init();
- pthread_mutex_lock(&(once_control->mutex));
- if (once_control->state == PTHREAD_NEEDS_INIT) {
- init_routine();
- once_control->state = PTHREAD_DONE_INIT;
- }
- pthread_mutex_unlock(&(once_control->mutex));
+ int wakeup = 0;
+
+ if (once_control->state == ONCE_DONE)
+ return (0);
+ _pthread_mutex_lock(&once_lock);
+ while (*(volatile int *)&(once_control->state) == ONCE_IN_PROGRESS)
+ _pthread_cond_wait(&once_cv, &once_lock);
+ /*
+ * If previous thread was canceled, then the state still
+ * could be ONCE_NEVER_DONE, we need to check it again.
+ */
+ if (*(volatile int *)&(once_control->state) == ONCE_NEVER_DONE) {
+ once_control->state = ONCE_IN_PROGRESS;
+ _pthread_mutex_unlock(&once_lock);
+ _pthread_cleanup_push(once_cancel_handler, once_control);
+ init_routine();
+ _pthread_cleanup_pop(0);
+ _pthread_mutex_lock(&once_lock);
+ once_control->state = ONCE_DONE;
+ wakeup = 1;
}
+ _pthread_mutex_unlock(&once_lock);
+ if (wakeup)
+ _pthread_cond_broadcast(&once_cv);
return (0);
}
+
OpenPOWER on IntegriCloud