Remove the option to spawn_thread for an evq (XCC)
[akaros.git] / user / parlib / dtls.c
index ed033ae..9edce00 100644 (file)
  */
 
 #include <stddef.h>
-#include "spinlock.h"
-#include "dtls.h"
-#include "slab.h"
+#include <assert.h>
+#include <parlib/spinlock.h>
+#include <parlib/dtls.h>
+#include <parlib/slab.h>
 
 /* The current dymamic tls implementation uses a locked linked list
  * to find the key for a given thread. We should probably find a better way to
  * do this based on a custom lock-free hash table or something. */
 #include <sys/queue.h>
-#include "spinlock.h"
+#include <parlib/spinlock.h>
+
+/* Define some number of static keys, for which the memory containing the keys
+ * and the per-thread memory for the values associated with those keys is
+ * allocated statically. This is adapted from glibc's notion of the
+ * "specific_1stblock" field embedded directly into its pthread structure for
+ * pthread_get/specific() calls. */
+#define NUM_STATIC_KEYS 32
 
 /* The dynamic tls key structure */
 struct dtls_key {
-  spinlock_t lock;
+  int id;
   int ref_count;
   bool valid;
   void (*dtor)(void*);
@@ -50,7 +58,8 @@ TAILQ_HEAD(dtls_list, dtls_value);
 typedef struct dtls_data {
   /* A per-thread list of dtls regions */
   struct dtls_list list;
-
+  /* Memory to hold dtls values for the first NUM_STATIC_KEYS keys */
+  struct dtls_value early_values[NUM_STATIC_KEYS];
 } dtls_data_t;
 
 /* A slab of dtls keys (global to all threads) */
@@ -59,57 +68,76 @@ static struct kmem_cache *__dtls_keys_cache;
 /* A slab of values for use when mapping a dtls_key to its per-thread value */
 struct kmem_cache *__dtls_values_cache;
   
-/* A lock protecting access to the caches above */
-static spinlock_t __dtls_lock;
-
 static __thread dtls_data_t __dtls_data;
 static __thread bool __dtls_initialized = false;
+static struct dtls_key static_dtls_keys[NUM_STATIC_KEYS];
+static int num_dtls_keys = 0;
+
+/* Initialize the slab caches for allocating dtls keys and values. */
+int dtls_cache_init()
+{
+  /* Make sure this only runs once */
+  static bool initialized = false;
+  if (initialized)
+      return 0;
+  initialized = true;
+
+  /* Initialize the global cache of dtls_keys */
+  __dtls_keys_cache = kmem_cache_create("dtls_keys_cache",
+    sizeof(struct dtls_key), __alignof__(struct dtls_key), 0, NULL, NULL);
+
+  /* Initialize the global cache of dtls_values */
+  __dtls_values_cache = kmem_cache_create("dtls_values_cache",
+    sizeof(struct dtls_value), __alignof__(struct dtls_value), 0, NULL, NULL);
+
+  return 0;
+}
 
 static dtls_key_t __allocate_dtls_key() 
 {
-  spinlock_lock(&__dtls_lock);
-  dtls_key_t key = kmem_cache_alloc(__dtls_keys_cache, 0);
+  dtls_key_t key;
+  int keyid = __sync_fetch_and_add(&num_dtls_keys, 1);
+  if (keyid < NUM_STATIC_KEYS) {
+    key = &static_dtls_keys[keyid];
+  } else {
+    dtls_cache_init();
+    key = kmem_cache_alloc(__dtls_keys_cache, 0);
+  }
   assert(key);
+  key->id = keyid;
   key->ref_count = 1;
-  spinlock_unlock(&__dtls_lock);
   return key;
 }
 
 static void __maybe_free_dtls_key(dtls_key_t key)
 {
-  if(key->ref_count == 0) {
-    spinlock_lock(&__dtls_lock);
+  int ref_count = __sync_add_and_fetch(&key->ref_count, -1);
+  if (ref_count == 0 && key->id >= NUM_STATIC_KEYS)
     kmem_cache_free(__dtls_keys_cache, key);
-    spinlock_unlock(&__dtls_lock);
+}
+
+static struct dtls_value *__allocate_dtls_value(struct dtls_data *dtls_data,
+                                                struct dtls_key *key)
+{
+  struct dtls_value *v;
+  if (key->id < NUM_STATIC_KEYS) {
+    v = &dtls_data->early_values[key->id];
+  } else {
+    v = kmem_cache_alloc(__dtls_values_cache, 0);
   }
+  assert(v);
+  return v;
 }
 
-/* Constructor to get a reference to the main thread's TLS descriptor */
-int dtls_lib_init()
+static void __free_dtls_value(struct dtls_value *v)
 {
-  /* Make sure this only runs once */
-  static bool initialized = false;
-  if (initialized)
-      return 0;
-  initialized = true;
-  
-  /* Initialize the global cache of dtls_keys */
-  __dtls_keys_cache = kmem_cache_create("dtls_keys_cache", 
-    sizeof(struct dtls_key), __alignof__(struct dtls_key), 0, NULL, NULL);
-  
-  __dtls_values_cache = kmem_cache_create("dtls_values_cache", 
-    sizeof(struct dtls_value), __alignof__(struct dtls_value), 0, NULL, NULL);
-  
-  /* Initialize the lock that protects the cache */
-  spinlock_init(&__dtls_lock);
-  return 0;
+  if (v->key->id >= NUM_STATIC_KEYS)
+    kmem_cache_free(__dtls_values_cache, v);
 }
 
 dtls_key_t dtls_key_create(dtls_dtor_t dtor)
 {
-  dtls_lib_init();
   dtls_key_t key = __allocate_dtls_key();
-  spinlock_init(&key->lock);
   key->valid = true;
   key->dtor = dtor;
   return key;
@@ -118,45 +146,39 @@ dtls_key_t dtls_key_create(dtls_dtor_t dtor)
 void dtls_key_delete(dtls_key_t key)
 {
   assert(key);
-
-  spinlock_lock(&key->lock);
   key->valid = false;
-  key->ref_count--;
-  spinlock_unlock(&key->lock);
   __maybe_free_dtls_key(key);
 }
 
-static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key, void *dtls)
+static inline void *__get_dtls(dtls_data_t *dtls_data, dtls_key_t key)
 {
   assert(key);
 
-  spinlock_lock(&key->lock);
-  key->ref_count++;
-  spinlock_unlock(&key->lock);
-
-  struct dtls_value *v = NULL;
-  TAILQ_FOREACH(v, &dtls_data->list, link)
-    if(v->key == key) break;
-
-  if(!v) {
-    spinlock_lock(&__dtls_lock);
-    v = kmem_cache_alloc(__dtls_values_cache, 0);
-    spinlock_unlock(&__dtls_lock);
-    assert(v);
-    v->key = key;
-    TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
+  struct dtls_value *v;
+  if (key->id < NUM_STATIC_KEYS) {
+    v = &dtls_data->early_values[key->id];
+    if (v->key != NULL)
+      return v->dtls;
+  } else {
+    TAILQ_FOREACH(v, &dtls_data->list, link)
+      if (v->key == key)
+        return v->dtls;
   }
-  v->dtls = dtls;
+  return NULL;
 }
 
-static inline void *__get_dtls(dtls_data_t *dtls_data, dtls_key_t key)
+static inline void __set_dtls(dtls_data_t *dtls_data, dtls_key_t key, void *dtls)
 {
   assert(key);
+  __sync_fetch_and_add(&key->ref_count, 1);
 
-  struct dtls_value *v = NULL;
-  TAILQ_FOREACH(v, &dtls_data->list, link)
-    if(v->key == key) return v->dtls;
-  return v;
+  struct dtls_value *v = __get_dtls(dtls_data, key);
+  if (!v) {
+    v = __allocate_dtls_value(dtls_data, key);
+    v->key = key;
+    TAILQ_INSERT_HEAD(&dtls_data->list, v, link);
+  }
+  v->dtls = dtls;
 }
 
 static inline void __destroy_dtls(dtls_data_t *dtls_data)
@@ -165,15 +187,8 @@ static inline void __destroy_dtls(dtls_data_t *dtls_data)
   v = TAILQ_FIRST(&dtls_data->list);
   while(v != NULL) {
     dtls_key_t key = v->key;
-    bool run_dtor = false;
   
-    spinlock_lock(&key->lock);
-    if(key->valid)
-      if(key->dtor)
-        run_dtor = true;
-    spinlock_unlock(&key->lock);
-
-       // MUST run the dtor outside the spinlock if we want it to be able to call
+       // The dtor must be called outside of a spinlock so that it can call
        // code that may deschedule it for a while (i.e. a mutex). Probably a
        // good idea anyway since it can be arbitrarily long and is written by the
        // user. Note, there is a small race here on the valid field, whereby we
@@ -181,22 +196,16 @@ static inline void __destroy_dtls(dtls_data_t *dtls_data)
        // be deleted though, as protected by the ref count. Any reasonable usage
        // of this interface should safeguard that a key is never destroyed before
        // all of the threads that use it have exited anyway.
-    if(run_dtor) {
+    if (key->valid && key->dtor) {
          void *dtls = v->dtls;
       v->dtls = NULL;
       key->dtor(dtls);
     }
-
-    spinlock_lock(&key->lock);
-    key->ref_count--;
-    spinlock_unlock(&key->lock);
     __maybe_free_dtls_key(key);
 
     n = TAILQ_NEXT(v, link);
     TAILQ_REMOVE(&dtls_data->list, v, link);
-    spinlock_lock(&__dtls_lock);
-    kmem_cache_free(__dtls_values_cache, v);
-    spinlock_unlock(&__dtls_lock);
+    __free_dtls_value(v);
     v = n;
   }
 }