thread: add support for one-time global initialization
authorZaheer Chothia <zchothia@inf.ethz.ch>
Sun, 14 Dec 2014 19:11:26 +0000 (20:11 +0100)
committerZaheer Chothia <zchothia@inf.ethz.ch>
Mon, 20 Apr 2015 21:09:19 +0000 (23:09 +0200)
Summary:
This is an action that needs to be coordinated globally but the implementation
doesn't have to be as pessimistic.  The initialization function is only run
once and the common case is checking whether it has already been run.  The idea
here is to have a fast and slow path and let each thread use a locally cached
value.

Test Plan:
Tested with the small code snippet below. Initialization function ran
correctly (and only once).  For correctness of the locking scheme I
defer to the proposal document referred to in the header comment of
`lib/barrelfish/thread_once.c`.

  lang=c
  #include <barrelfish/threads.h>

  static thread_once_t init = THREAD_ONCE_INIT;

  static void init_func(void) {
    printf("init\n");
  }

  int main() {
    thread_once(&init, init_func);
    printf("Hello World\n");
    thread_once(&init, init_func);
    return 0;
  }

Differential Revision: https://code.systems.ethz.ch/D9

Signed-off-by: Zaheer Chothia <zchothia@inf.ethz.ch>

include/barrelfish/threads.h
lib/barrelfish/Hakefile
lib/barrelfish/thread_once.c [new file with mode: 0644]

index 83a2fbc..39c8d69 100644 (file)
 #ifndef LIBBARRELFISH_THREADS_H
 #define LIBBARRELFISH_THREADS_H
 
+#include <assert.h>
+#include <limits.h>
+#include <sys/cdefs.h>
+
 #include <barrelfish/caddr.h> // for struct capref.
 #include <barrelfish/thread_sync.h>
-#include <barrelfish/caddr.h>
 #include <barrelfish_kpi/registers_arch.h>
 #include <barrelfish_kpi/dispatcher_handle.h>
 #include <errors/errno.h>
-#include <sys/cdefs.h>
 
 __BEGIN_DECLS
 
@@ -74,6 +76,27 @@ uintptr_t thread_id(void);
 uintptr_t thread_get_id(struct thread *t);
 void thread_set_id(uintptr_t id);
 
+typedef int thread_once_t;
+#define THREAD_ONCE_INIT INT_MAX
+
+extern __thread thread_once_t thread_once_local_epoch;
+extern void thread_once_internal(thread_once_t *control, void (*func)(void));
+
+/**
+ * \brief Run a routine exactly once; use this for thread-safe initialization.
+ *
+ * \param control Control word - should be initialized with THREAD_ONCE_INIT.
+ * \param func Callback to be invoked.
+ */
+static inline void thread_once(thread_once_t *control, void (*func)(void)) {
+    assert(control != NULL);
+    assert(func != NULL);
+    thread_once_t x = *control; // unprotected access
+    if (x > thread_once_local_epoch) {
+        thread_once_internal(control, func);
+    }
+}
+
 __END_DECLS
 
-#endif
+#endif  // LIBBARRELFISH_THREADS_H
index 8f94d08..4a3256e 100644 (file)
@@ -13,7 +13,7 @@
 
 [(let arch_dir = "arch" ./. archFamily arch
       common_srcs = [ "capabilities.c", "init.c", "dispatch.c", "threads.c",
-                      "thread_sync.c", "slab.c", "domain.c", "idc.c",
+                      "thread_once.c", "thread_sync.c", "slab.c", "domain.c", "idc.c",
                       "waitset.c", "event_queue.c", "event_mutex.c",
                       "idc_export.c", "nameservice_client.c", "msgbuf.c",
                       "monitor_client.c", "flounder_support.c", "flounder_glue_binding.c",
diff --git a/lib/barrelfish/thread_once.c b/lib/barrelfish/thread_once.c
new file mode 100644 (file)
index 0000000..f72c278
--- /dev/null
@@ -0,0 +1,71 @@
+/* Copyright (c) 2007, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// Taken from C++11 proposal N2660:
+//   "Dynamic Initialization and Destruction with Concurrency"
+//   http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2660.htm
+
+#include <limits.h>
+
+#include <barrelfish/threads.h>
+
+/// Protects global_epoch and all thread_once_t writes.
+static struct thread_mutex thread_once_mutex = THREAD_MUTEX_INITIALIZER;
+
+/// Signalled whenever a fast_pthread_once_t is finalized.
+static struct thread_cond thread_once_condvar = THREAD_COND_INITIALIZER;
+
+#define THREAD_ONCE_BEING_INITIALIZED (THREAD_ONCE_INIT - 1)
+
+static thread_once_t thread_once_global_epoch = 0;
+
+__thread thread_once_t thread_once_local_epoch;
+
+void thread_once_internal(thread_once_t *control, void (*func)(void)) {
+    thread_once_t x = *control; // unprotected access
+    if (x > thread_once_local_epoch) {
+        thread_mutex_lock(&thread_once_mutex);
+        if (*control == THREAD_ONCE_INIT) {
+            *control = THREAD_ONCE_BEING_INITIALIZED;
+            thread_mutex_unlock(&thread_once_mutex);
+            (*func)();
+            thread_mutex_lock(&thread_once_mutex);
+            thread_once_global_epoch++;
+            *control = thread_once_global_epoch;
+            thread_cond_broadcast(&thread_once_condvar);
+        } else {
+            while (*control == THREAD_ONCE_BEING_INITIALIZED) {
+                thread_cond_wait(&thread_once_condvar, &thread_once_mutex);
+            }
+        }
+        thread_once_local_epoch = thread_once_global_epoch;
+        thread_mutex_unlock(&thread_once_mutex);
+    }
+}