OSSL_NAMEMAP *ossl_namemap_new(OSSL_LIB_CTX *libctx)
{
OSSL_NAMEMAP *namemap;
- HT_CONFIG htconf = { NULL, NULL, NULL, NAMEMAP_HT_BUCKETS, 1, 1 };
+ HT_CONFIG htconf = { NULL, NULL, NULL, NAMEMAP_HT_BUCKETS, 1, 1, 0 };
htconf.ctx = libctx;
# include <sanitizer/tsan_interface.h>
#endif
-#include "internal/numbers.h"
/*
* When we do a lookup/insert/delete, there is a high likelihood
* that we will iterate over at least part of the neighborhood list
if (new == NULL)
return NULL;
- new->atomic_lock = CRYPTO_THREAD_lock_new();
- if (new->atomic_lock == NULL)
+ if (conf->lockless_reads && conf->no_rcu)
goto err;
+ if (!conf->no_rcu) {
+ new->atomic_lock = CRYPTO_THREAD_lock_new();
+ if (new->atomic_lock == NULL)
+ goto err;
+ }
memcpy(&new->config, conf, sizeof(*conf));
if (new->config.init_neighborhoods != 0) {
goto err;
new->md->neighborhood_mask = new->wpd.neighborhood_len - 1;
- new->lock = ossl_rcu_lock_new(1, conf->ctx);
- if (new->lock == NULL)
- goto err;
-
+ if (!conf->no_rcu) {
+ new->lock = ossl_rcu_lock_new(1, conf->ctx);
+ if (new->lock == NULL)
+ goto err;
+ }
if (new->config.ht_hash_fn == NULL)
new->config.ht_hash_fn = internal_ht_hash_fn;
return new;
err:
- CRYPTO_THREAD_lock_free(new->atomic_lock);
- ossl_rcu_lock_free(new->lock);
+ if (!conf->no_rcu) {
+ CRYPTO_THREAD_lock_free(new->atomic_lock);
+ ossl_rcu_lock_free(new->lock);
+ }
if (new->md != NULL)
OPENSSL_free(new->md->neighborhood_ptr_to_free);
OPENSSL_free(new->md);
int ossl_ht_read_lock(HT *htable)
{
+ if (htable->config.no_rcu)
+ return 1;
+
return ossl_rcu_read_lock(htable->lock);
}
void ossl_ht_read_unlock(HT *htable)
{
+ if (htable->config.no_rcu)
+ return;
+
ossl_rcu_read_unlock(htable->lock);
}
void ossl_ht_write_lock(HT *htable)
{
+ if (htable->config.no_rcu)
+ return;
+
ossl_rcu_write_lock(htable->lock);
htable->wpd.need_sync = 0;
}
{
int need_sync = htable->wpd.need_sync;
+ if (htable->config.no_rcu)
+ return;
+
htable->wpd.need_sync = 0;
ossl_rcu_write_unlock(htable->lock);
if (need_sync)
newmd->neighborhood_mask = DEFAULT_NEIGH_LEN - 1;
/* Swap the old and new mutable data sets */
- oldmd = ossl_rcu_deref(&h->md);
- ossl_rcu_assign_ptr(&h->md, &newmd);
+ if (!h->config.no_rcu) {
+ oldmd = ossl_rcu_deref(&h->md);
+ ossl_rcu_assign_ptr(&h->md, &newmd);
+ } else {
+ oldmd = h->md;
+ h->md = newmd;
+ }
/* Set the number of entries to 0 */
h->wpd.value_count = 0;
h->wpd.neighborhood_len = DEFAULT_NEIGH_LEN;
- ossl_rcu_call(h->lock, free_oldmd, oldmd);
+ if (!h->config.no_rcu) {
+ ossl_rcu_call(h->lock, free_oldmd, oldmd);
+ } else {
+ free_oldmd(oldmd);
+ }
h->wpd.need_sync = 1;
+
return 1;
}
ossl_ht_flush_internal(h);
ossl_ht_write_unlock(h);
/* Freeing the lock does a final sync for us */
- CRYPTO_THREAD_lock_free(h->atomic_lock);
- ossl_rcu_lock_free(h->lock);
+ if (!h->config.no_rcu) {
+ CRYPTO_THREAD_lock_free(h->atomic_lock);
+ ossl_rcu_lock_free(h->lock);
+ }
OPENSSL_free(h->md->neighborhood_ptr_to_free);
OPENSSL_free(h->md);
OPENSSL_free(h);
/*
* Now we replace the old mutable data with the new
*/
- ossl_rcu_assign_ptr(&h->md, &newmd);
- ossl_rcu_call(h->lock, free_old_neigh_table, oldmd);
- h->wpd.need_sync = 1;
+ if (!h->config.no_rcu) {
+ ossl_rcu_assign_ptr(&h->md, &newmd);
+ ossl_rcu_call(h->lock, free_old_neigh_table, oldmd);
+ h->wpd.need_sync = 1;
+ } else {
+ h->md = newmd;
+ free_old_neigh_table(oldmd);
+ }
/*
* And we're done
*/
PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
- ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
+ if (!h->config.no_rcu)
+ ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
+ else
+ ival = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
if (ival == NULL) {
empty_idx = j;
/* lockless_reads implies no deletion, we can break out */
goto not_found;
continue;
}
- if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
- &ihash, h->atomic_lock))
- return 0;
+ if (!h->config.no_rcu) {
+ if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
+ &ihash, h->atomic_lock))
+ return 0;
+ } else {
+ ihash = md->neighborhoods[neigh_idx].entries[j].hash;
+ }
if (compare_hash(hash, ihash) && match_key(&newval->value.key,
&ival->key)) {
if (olddata == NULL) {
return 0;
}
/* Do a replacement */
- if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[j].hash,
- hash, h->atomic_lock))
- return 0;
- *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
- ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[j].value,
- &newval);
- ossl_rcu_call(h->lock, free_old_ht_value, *olddata);
+ if (!h->config.no_rcu) {
+ if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[j].hash,
+ hash, h->atomic_lock))
+ return 0;
+ *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
+ ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[j].value,
+ &newval);
+ ossl_rcu_call(h->lock, free_old_ht_value, *olddata);
+ } else {
+ md->neighborhoods[neigh_idx].entries[j].hash = hash;
+ *olddata = (HT_VALUE *)md->neighborhoods[neigh_idx].entries[j].value;
+ md->neighborhoods[neigh_idx].entries[j].value = newval;
+ }
h->wpd.need_sync = 1;
return 1;
}
/* If we get to here, its just an insert */
if (empty_idx == SIZE_MAX)
return -1; /* out of space */
- if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[empty_idx].hash,
- hash, h->atomic_lock))
- return 0;
+ if (!h->config.no_rcu) {
+ if (!CRYPTO_atomic_store(&md->neighborhoods[neigh_idx].entries[empty_idx].hash,
+ hash, h->atomic_lock))
+ return 0;
+ ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[empty_idx].value,
+ &newval);
+ } else {
+ md->neighborhoods[neigh_idx].entries[empty_idx].hash = hash;
+ md->neighborhoods[neigh_idx].entries[empty_idx].value = newval;
+ }
h->wpd.value_count++;
- ossl_rcu_assign_ptr(&md->neighborhoods[neigh_idx].entries[empty_idx].value,
- &newval);
return 1;
}
hash = h->config.ht_hash_fn(key);
- md = ossl_rcu_deref(&h->md);
+ if (!h->config.no_rcu)
+ md = ossl_rcu_deref(&h->md);
+ else
+ md = h->md;
neigh_idx = neigh_idx_start = hash & md->neighborhood_mask;
do {
PREFETCH_NEIGHBORHOOD(md->neighborhoods[neigh_idx]);
for (j = 0; j < NEIGHBORHOOD_LEN; j++) {
- ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
+ if (!h->config.no_rcu)
+ ival = ossl_rcu_deref(&md->neighborhoods[neigh_idx].entries[j].value);
+ else
+ ival = md->neighborhoods[neigh_idx].entries[j].value;
if (ival == NULL) {
if (lockless_reads)
/* lockless_reads implies no deletion, we can break out */
return NULL;
continue;
}
- if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
- &ehash, h->atomic_lock))
- return NULL;
+ if (!h->config.no_rcu) {
+ if (!CRYPTO_atomic_load(&md->neighborhoods[neigh_idx].entries[j].hash,
+ &ehash, h->atomic_lock))
+ return NULL;
+ } else {
+ ehash = md->neighborhoods[neigh_idx].entries[j].hash;
+ }
if (compare_hash(hash, ehash) && match_key(&ival->value.key, key))
return (HT_VALUE *)ival;
}
continue;
if (compare_hash(hash, h->md->neighborhoods[neigh_idx].entries[j].hash)
&& match_key(key, &v->value.key)) {
- if (!CRYPTO_atomic_store(&h->md->neighborhoods[neigh_idx].entries[j].hash,
- 0, h->atomic_lock))
- break;
+ if (!h->config.no_rcu) {
+ if (!CRYPTO_atomic_store(&h->md->neighborhoods[neigh_idx].entries[j].hash,
+ 0, h->atomic_lock))
+ break;
+ ossl_rcu_assign_ptr(&h->md->neighborhoods[neigh_idx].entries[j].value, &nv);
+ } else {
+ h->md->neighborhoods[neigh_idx].entries[j].hash = 0;
+ h->md->neighborhoods[neigh_idx].entries[j].value = NULL;
+ }
h->wpd.value_count--;
- ossl_rcu_assign_ptr(&h->md->neighborhoods[neigh_idx].entries[j].value,
- &nv);
rc = 1;
break;
}
}
if (rc == 1) {
- ossl_rcu_call(h->lock, free_old_entry, v);
+ if (!h->config.no_rcu)
+ ossl_rcu_call(h->lock, free_old_entry, v);
+ else
+ free_old_entry(v);
h->wpd.need_sync = 1;
}
+
return rc;
}
+
+HT_VALUE *ossl_ht_deref_value(HT *h, HT_VALUE **val)
+{
+ HT_VALUE *v;
+
+ if (!h->config.no_rcu)
+ v = ossl_rcu_deref(val);
+ else
+ v = *val;
+
+ return v;
+}
+
+void *ossl_ht_inner_value(HT *h, HT_VALUE *v)
+{
+ void *inner;
+
+ if (!h->config.no_rcu) {
+ inner = v->value;
+ } else {
+ inner = v->value;
+ OPENSSL_free(v);
+ }
+
+ return inner;
+}
I<ht_free_fn> The function to call to free a value, may be NULL.
I<ht_hash_fn> The function to generate a hash value for a key, may be NULL.
I<init_neighborhoods> The initial number of neighborhoods in the hash table.
+ I<no_rcu> Disables RCU locking.
Note that init_bucket_len may be set to zero, which will use the default initial
bucket size, which will be automatically expanded with the hash table load
is the callers responsibility in this mode of operation to provide thread
synchronization.
+Note that if RCU locks are disabled, calls to ossl_ht_[read/write]_[lock/unlock]
+have no effect.
+
=item *
ossl_ht_free() frees an allocated hash table. Each element in the table
=item *
ossl_ht_insert() inserts an B<HT_VALUE> element into the hash table, to be
-hashed using the corresponding B<HT_KEY> value.
+hashed using the corresponding B<HT_KEY> value. The returned pointer olddata must
+be freed only when RCU is disabled and insert performs a replacement.
=item *
int FuzzerInitialize(int *argc, char ***argv)
{
- HT_CONFIG fuzz_conf = {NULL, fuzz_free_cb, NULL, 0, 1};
+ HT_CONFIG fuzz_conf = {NULL, fuzz_free_cb, NULL, 0, 1, 0};
OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
ERR_clear_error();
size_t init_neighborhoods;
uint32_t collision_check;
uint32_t lockless_reads;
+ uint32_t no_rcu;
} HT_CONFIG;
/*
inval.type_id = &name##_##vtype##_id; \
rc = ossl_ht_insert(h, key, &inval, olddata == NULL ? NULL : &oval); \
if (oval != NULL) \
- *olddata = (vtype *)oval->value; \
+ *olddata = (vtype *)ossl_ht_inner_value(h, oval); \
return rc; \
} \
\
vv = ossl_ht_get(h, key); \
if (vv == NULL) \
return NULL; \
- *v = ossl_rcu_deref(&vv); \
+ *v = ossl_ht_deref_value(h, &vv); \
return ossl_ht_##name##_##vtype##_from_value(*v); \
} \
\
*/
HT_VALUE *ossl_ht_get(HT *htable, HT_KEY *key);
+/**
+ * ossl_ht_deref_value - Dereference a value stored in a hash table entry
+ * @h: The hash table handle
+ * @val: Pointer to the value pointer inside the hash table
+ *
+ * This helper returns the actual value stored in a hash table entry,
+ * with awareness of whether the table is configured for RCU (Read-Copy-Update)
+ * safe lookups.
+ *
+ * If the hash table is configured to use RCU lookups, the function
+ * calls ossl_rcu_deref() to safely read the value under RCU protection.
+ * This ensures that the caller sees a consistent pointer in concurrent environments.
+ *
+ * If RCU is not enabled (i.e. `h->config.no_rcu` is true), the function
+ * simply dereferences @val directly.
+ *
+ * Return:
+ * A pointer to the dereferenced hash table value (`HT_VALUE *`), or NULL if
+ * the underlying pointer is NULL.
+ */
+HT_VALUE *ossl_ht_deref_value(HT *p, HT_VALUE **val);
+
+/**
+ * ossl_ht_inner_value - Extract the user payload from a hash table value
+ * @h: The hash table handle
+ * @v: The hash table value wrapper (HT_VALUE)
+ *
+ * This helper returns the user-provided payload stored inside a
+ * hash table value container. The behavior differs depending on
+ * whether the hash table is configured to use RCU (Read-Copy-Update)
+ * for concurrency control.
+ *
+ * - If RCU is enabled, the function simply returns `v->value` without
+ * modifying or freeing the container.
+ *
+ * - If RCU is disabled the container structure `v` is no longer needed once
+ * the inner pointer has been extracted. In this case, the function frees
+ * `v` and returns the inner `value` pointer directly.
+ *
+ * Return:
+ * A pointer to the user payload (`void *`) contained in the hash table
+ * value wrapper.
+ */
+void *ossl_ht_inner_value(HT *h, HT_VALUE *v);
+
#endif
return (uint64_t)(*(uint32_t *)key->keybuf);
}
-static int test_int_hashtable(void)
+static int test_int_hashtable(int idx)
{
static struct {
int data;
};
const size_t n_dels = OSSL_NELEM(dels);
HT_CONFIG hash_conf = {
- NULL,
- NULL,
- NULL,
- 0,
- 1,
+ .collision_check = 1,
+ .no_rcu = idx,
};
INTKEY key;
int rc = 0;
unsigned int i;
int testresult = 0, *p;
HT_CONFIG hash_conf = {
- NULL, /* use default context */
- hashtable_intfree, /* our free function */
- hashtable_hash, /* our hash function */
- 625000, /* preset hash size */
- 1, /* Check collisions */
- 0 /* Lockless reads */
+ .ht_free_fn = hashtable_intfree,
+ .ht_hash_fn = hashtable_hash,
+ .init_neighborhoods = 625000,
+ .collision_check = 1,
+ .lockless_reads = idx % 2,
+ .no_rcu = idx / 2,
};
+
HT *h;
INTKEY key;
HT_VALUE *v;
struct timeval start, end, delta;
#endif
- hash_conf.lockless_reads = idx;
h = ossl_ht_new(&hash_conf);
-
+ if (h == NULL
+ && hash_conf.no_rcu
+ && hash_conf.lockless_reads)
+ return 1;
if (!TEST_ptr(h))
goto end;
const int j = (7 * i + 4) % n * 3 + 1;
HT_SET_KEY_FIELD(&key, mykey, j);
- switch (idx) {
+ switch (idx % 2) {
case 0:
if (!TEST_int_eq((ossl_ht_delete(h, TO_HT_KEY(&key))), 1)) {
TEST_info("hashtable didn't delete key %d\n", j);
IMPLEMENT_HT_VALUE_TYPE_FNS(TEST_MT_ENTRY, mt, static)
+struct ht_internal_st {
+ HT_CONFIG config;
+};
+
static int worker_num = 0;
static CRYPTO_RWLOCK *worker_lock;
static CRYPTO_RWLOCK *testrand_lock;
+static CRYPTO_RWLOCK *no_rcu_lock;
static int free_failure = 0;
static int shutting_down = 0;
static int global_iteration = 0;
}
switch(behavior) {
case DO_LOOKUP:
- if (!ossl_ht_read_lock(m_ht))
- break;
+ if (!m_ht->config.no_rcu) {
+ if (!ossl_ht_read_lock(m_ht))
+ break;
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_read_lock(no_rcu_lock)))
+ break;
+ }
m = ossl_ht_mt_TEST_MT_ENTRY_get(m_ht, TO_HT_KEY(&key), &v);
if (m != NULL && m != expected_m) {
worker_exits[num] = "Read unexpected value from hashtable";
TEST_info("Iteration %d Read unexpected value %p when %p expected",
giter, (void *)m, (void *)expected_m);
}
- ossl_ht_read_unlock(m_ht);
+ if (!m_ht->config.no_rcu) {
+ ossl_ht_read_unlock(m_ht);
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_unlock(no_rcu_lock)))
+ break;
+ }
if (worker_exits[num] != NULL)
return;
break;
case DO_INSERT:
case DO_REPLACE:
- ossl_ht_write_lock(m_ht);
+ if (!m_ht->config.no_rcu) {
+ ossl_ht_write_lock(m_ht);
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_write_lock(no_rcu_lock)))
+ break;
+ }
if (behavior == DO_REPLACE) {
expected_rc = 1;
r = &m;
}
if (expected_rc == 1)
expected_m->in_table = 1;
- ossl_ht_write_unlock(m_ht);
+ if (!m_ht->config.no_rcu) {
+ ossl_ht_write_unlock(m_ht);
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_unlock(no_rcu_lock)))
+ break;
+ }
if (worker_exits[num] != NULL)
return;
break;
case DO_DELETE:
- ossl_ht_write_lock(m_ht);
+ if (!m_ht->config.no_rcu) {
+ ossl_ht_write_lock(m_ht);
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_write_lock(no_rcu_lock)))
+ break;
+ }
expected_rc = expected_m->in_table;
if (expected_rc == 1) {
/*
expected_m->in_table ? "in table" : "not in table");
worker_exits[num] = "Failure on delete";
}
- ossl_ht_write_unlock(m_ht);
+ if (!m_ht->config.no_rcu) {
+ ossl_ht_write_unlock(m_ht);
+ } else {
+ if (!TEST_true(CRYPTO_THREAD_unlock(no_rcu_lock)))
+ break;
+ }
if (worker_exits[num] != NULL)
return;
break;
}
}
-static int test_hashtable_multithread(void)
+static int test_hashtable_multithread(int idx)
{
HT_CONFIG hash_conf = {
- NULL, /* use default context */
- hashtable_mt_free, /* our free function */
- NULL, /* default hash function */
- 0, /* default hash size */
- 1, /* Check collisions */
+ .ht_free_fn = hashtable_mt_free,
+ .init_neighborhoods = 0,
+ .collision_check = 1,
+ .no_rcu = idx,
};
int ret = 0;
thread_t workers[NUM_WORKERS];
goto end_free;
if (!TEST_ptr(testrand_lock = CRYPTO_THREAD_lock_new()))
goto end_free;
+ if (!TEST_ptr(no_rcu_lock = CRYPTO_THREAD_lock_new()))
+ goto end_free;
#ifdef MEASURE_HASH_PERFORMANCE
gettimeofday(&start, NULL);
#endif
TEST_info("multithread stress runs 40000 ops in %ld.%ld seconds", delta.tv_sec, delta.tv_usec);
#endif
+ worker_num = 0;
+ free_failure = 0;
+ global_iteration = 0;
end_free:
shutting_down = 1;
ossl_ht_free(m_ht);
CRYPTO_THREAD_lock_free(worker_lock);
CRYPTO_THREAD_lock_free(testrand_lock);
+ CRYPTO_THREAD_lock_free(no_rcu_lock);
end:
return ret;
}
{
ADD_TEST(test_int_lhash);
ADD_TEST(test_stress);
- ADD_TEST(test_int_hashtable);
- ADD_ALL_TESTS(test_hashtable_stress, 2);
- ADD_TEST(test_hashtable_multithread);
+ ADD_ALL_TESTS(test_int_hashtable, 2);
+ ADD_ALL_TESTS(test_hashtable_stress, 4);
+ ADD_ALL_TESTS(test_hashtable_multithread, 2);
return 1;
}