mirror of https://github.com/openssl/openssl
adding a multithreaded hashtable test
Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Paul Dale <pauli@openssl.org> (Merged from https://github.com/openssl/openssl/pull/23671)
This commit is contained in:
parent
f597acb71b
commit
2a54ec0bdd
|
@ -53,19 +53,19 @@ IMPLEMENT_HT_VALUE_TYPE_FNS
|
||||||
|
|
||||||
This API provides a library-internal implementation of a hashtable that provides
|
This API provides a library-internal implementation of a hashtable that provides
|
||||||
reference counted object retrieval under the protection of an rcu lock. API
|
reference counted object retrieval under the protection of an rcu lock. API
|
||||||
type safety is offered via conversion macros to and from the generic HT_VALUE
|
type safety is offered via conversion macros to and from the generic B<HT_VALUE>
|
||||||
type.
|
type.
|
||||||
|
|
||||||
=over 2
|
=over 2
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
ossl_ht_new() returns a new HT (hashtable object) used to store data
|
ossl_ht_new() returns a new B<HT> (hashtable object) used to store data
|
||||||
elements based on a defined key. The call accepts an HT_CONFIG pointer which
|
elements based on a defined key. The call accepts an HT_CONFIG pointer which
|
||||||
contains configurations options for hashtable. Current config options consist
|
contains configurations options for hashtable. Current config options consist
|
||||||
of:
|
of:
|
||||||
I<ht_free_fn> The function to call to free a value, may be B<NULL>.
|
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 B<NULL>.
|
I<ht_hash_fn> The function to generate a hash value for a key, may be NULL.
|
||||||
I<init_neighborhood_len> The initial number of neighborhoods in the hash table.
|
I<init_neighborhood_len> The initial number of neighborhoods in the hash table.
|
||||||
|
|
||||||
Note that init_bucket_len may be set to zero, which will use the default initial
|
Note that init_bucket_len may be set to zero, which will use the default initial
|
||||||
|
@ -109,13 +109,13 @@ called to release the element data.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
ossl_ht_insert() inserts an HT_VALUE element into the hash table, to be
|
ossl_ht_insert() inserts an B<HT_VALUE> element into the hash table, to be
|
||||||
hashed using the corresponding HT_KEY value.
|
hashed using the corresponding B<HT_KEY> value.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
ossl_ht_delete() deletes an entry from the hashtable indexed by the passed
|
ossl_ht_delete() deletes an entry from the hashtable indexed by the passed
|
||||||
HT_KEY value.
|
B<HT_KEY> value.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
|
@ -138,20 +138,20 @@ properly synchronize such modifications with other threads.
|
||||||
|
|
||||||
ossl_ht_filter() iterates over all elements of the hash table, calling
|
ossl_ht_filter() iterates over all elements of the hash table, calling
|
||||||
the filter callback for each element. If the callback returns 1, the
|
the filter callback for each element. If the callback returns 1, the
|
||||||
corresponding HT_VALUE is placed on a list, and its reference count incremented.
|
corresponding B<HT_VALUE> is placed on a list, and its reference count incremented.
|
||||||
The completed list is returned to the caller as an HT_VALUE_LIST object
|
The completed list is returned to the caller as an B<HT_VALUE_LIST> object
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
ossl_ht_value_list_free() frees an HT_VALUE_LIST. For each element on
|
ossl_ht_value_list_free() frees an B<HT_VALUE_LIST>. For each element on
|
||||||
the list, its reference count is decremented, and after traversing the list, the
|
the list, its reference count is decremented, and after traversing the list, the
|
||||||
list object is freed. Note, NULL elements are allowed on the list, but for any
|
list object is freed. Note, NULL elements are allowed on the list, but for any
|
||||||
element which is taken from the list by a caller, they must call
|
element which is taken from the list by a caller, they must call
|
||||||
ossl_ht_put on the HT_VALUE to prevent memory leaks.
|
ossl_ht_put() on the B<HT_VALUE> to prevent memory leaks.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
ossl_ht_get() preforms a lookup of an HT_KEY in the hashtable, returning
|
ossl_ht_get() preforms a lookup of an B<HT_KEY> in the hashtable, returning
|
||||||
its corresponding value.
|
its corresponding value.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
@ -240,23 +240,23 @@ pointer
|
||||||
|
|
||||||
<TYPE> ossl_ht_NAME_TYPE_get(HT *h, HT_KEY *key, HT_VALUE **v)
|
<TYPE> ossl_ht_NAME_TYPE_get(HT *h, HT_KEY *key, HT_VALUE **v)
|
||||||
Looks up an item in the hash table based on key, and returns the data it found,
|
Looks up an item in the hash table based on key, and returns the data it found,
|
||||||
if any. v holds a pointer to the HT_VALUE associated with the data.
|
if any. v holds a pointer to the B<HT_VALUE> associated with the data.
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
<TYPE> *ossl_ht_NAME_TYPE_from_value(HT_VALUE *v)
|
<TYPE> *ossl_ht_NAME_TYPE_from_value(HT_VALUE *v)
|
||||||
Validates that the HT_VALUE provided matches the TYPE specified, and returns the
|
Validates that the B<HT_VALUE> provided matches the TYPE specified, and returns the
|
||||||
value data. If there is a type mismatch, NULL is returned
|
value data. If there is a type mismatch, NULL is returned
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
HT_VALUE *ossl_ht_NAME_TYPE_to_value(<TYPE> *data)
|
HT_VALUE *ossl_ht_NAME_TYPE_to_value(<TYPE> *data)
|
||||||
Converts the data pointer provided to an HT_VALUE object
|
Converts the data pointer provided to an B<HT_VALUE> object
|
||||||
|
|
||||||
=item *
|
=item *
|
||||||
|
|
||||||
int ossl_ht_NAME_TYPE_type(HT_VALUE *v)
|
int ossl_ht_NAME_TYPE_type(HT_VALUE *v)
|
||||||
Returns true if the HT_VALUE object passed in is of type <TYPE>
|
Returns true if the B<HT_VALUE> object passed in is of type <TYPE>
|
||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
@ -264,7 +264,7 @@ Returns true if the HT_VALUE object passed in is of type <TYPE>
|
||||||
|
|
||||||
=head1 RETURN VALUES
|
=head1 RETURN VALUES
|
||||||
|
|
||||||
ossl_ht_new() returns an HT* struct on success and NULL on error
|
ossl_ht_new() returns an B<HT*> struct on success and NULL on error
|
||||||
|
|
||||||
void ossl_ht_free(HT *htable);
|
void ossl_ht_free(HT *htable);
|
||||||
|
|
||||||
|
@ -275,10 +275,10 @@ key was not found.
|
||||||
|
|
||||||
ossl_ht_count() returns the number of elements in the hash table
|
ossl_ht_count() returns the number of elements in the hash table
|
||||||
|
|
||||||
ossl_ht_filter() returns an HT_VALUE_LIST of all elements matching the
|
ossl_ht_filter() returns an B<HT_VALUE_LIST> of all elements matching the
|
||||||
provided filter
|
provided filter
|
||||||
|
|
||||||
ossl_ht_get() returns an HT_VALUE pointer, or NULL if the element was not
|
ossl_ht_get() returns an B<HT_VALUE> pointer, or NULL if the element was not
|
||||||
found.
|
found.
|
||||||
|
|
||||||
=head1 EXAMPLES
|
=head1 EXAMPLES
|
||||||
|
|
|
@ -14,10 +14,12 @@
|
||||||
#include <openssl/opensslconf.h>
|
#include <openssl/opensslconf.h>
|
||||||
#include <openssl/lhash.h>
|
#include <openssl/lhash.h>
|
||||||
#include <openssl/err.h>
|
#include <openssl/err.h>
|
||||||
|
#include <openssl/rand.h>
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
#include <internal/hashtable.h>
|
#include <internal/hashtable.h>
|
||||||
|
|
||||||
#include "internal/nelem.h"
|
#include "internal/nelem.h"
|
||||||
|
#include "threadstest.h"
|
||||||
#include "testutil.h"
|
#include "testutil.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -284,7 +286,8 @@ static int test_int_hashtable(void)
|
||||||
todel = ossl_ht_delete(ht, TO_HT_KEY(&key));
|
todel = ossl_ht_delete(ht, TO_HT_KEY(&key));
|
||||||
if (dels[i].should_del) {
|
if (dels[i].should_del) {
|
||||||
if (!TEST_int_eq(todel, 1)) {
|
if (!TEST_int_eq(todel, 1)) {
|
||||||
TEST_info("hashtable couldn't find entry to delete\n");
|
TEST_info("hashtable couldn't find entry %d to delete\n",
|
||||||
|
dels[i].data);
|
||||||
goto end;
|
goto end;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -464,11 +467,235 @@ end:
|
||||||
return testresult;
|
return testresult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct test_mt_entry {
|
||||||
|
int in_table;
|
||||||
|
int pending_delete;
|
||||||
|
} TEST_MT_ENTRY;
|
||||||
|
|
||||||
|
static HT *m_ht = NULL;
|
||||||
|
#define TEST_MT_POOL_SZ 256
|
||||||
|
#define TEST_THREAD_ITERATIONS 10000
|
||||||
|
|
||||||
|
static struct test_mt_entry test_mt_entries[TEST_MT_POOL_SZ];
|
||||||
|
static char *worker_exits[4];
|
||||||
|
|
||||||
|
HT_START_KEY_DEFN(mtkey)
|
||||||
|
HT_DEF_KEY_FIELD(index, unsigned int)
|
||||||
|
HT_END_KEY_DEFN(MTKEY)
|
||||||
|
|
||||||
|
IMPLEMENT_HT_VALUE_TYPE_FNS(TEST_MT_ENTRY, mt, static)
|
||||||
|
|
||||||
|
static int worker_num = 0;
|
||||||
|
static CRYPTO_RWLOCK *worker_lock;
|
||||||
|
static int free_failure = 0;
|
||||||
|
static int shutting_down = 0;
|
||||||
|
static int global_iteration = 0;
|
||||||
|
|
||||||
|
static void hashtable_mt_free(HT_VALUE *v)
|
||||||
|
{
|
||||||
|
TEST_MT_ENTRY *m = ossl_ht_mt_TEST_MT_ENTRY_from_value(v);
|
||||||
|
int pending_delete;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
CRYPTO_atomic_load_int(&m->pending_delete, &pending_delete, worker_lock);
|
||||||
|
|
||||||
|
if (shutting_down == 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (pending_delete == 0) {
|
||||||
|
TEST_info("Freeing element which was not scheduled for free");
|
||||||
|
free_failure = 1;
|
||||||
|
} else {
|
||||||
|
CRYPTO_atomic_add(&m->pending_delete, -1,
|
||||||
|
&ret, worker_lock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#define BEHAVIOR_MASK 0x3
|
||||||
|
#define DO_LOOKUP 0
|
||||||
|
#define DO_INSERT 1
|
||||||
|
#define DO_REPLACE 2
|
||||||
|
#define DO_DELETE 3
|
||||||
|
|
||||||
|
static void do_mt_hash_work(void)
|
||||||
|
{
|
||||||
|
MTKEY key;
|
||||||
|
unsigned int index;
|
||||||
|
int num;
|
||||||
|
TEST_MT_ENTRY *m;
|
||||||
|
TEST_MT_ENTRY *expected_m = NULL;
|
||||||
|
HT_VALUE *v = NULL;
|
||||||
|
TEST_MT_ENTRY **r = NULL;
|
||||||
|
int expected_rc;
|
||||||
|
int ret;
|
||||||
|
char behavior;
|
||||||
|
size_t iter = 0;
|
||||||
|
int giter;
|
||||||
|
|
||||||
|
CRYPTO_atomic_add(&worker_num, 1, &num, worker_lock);
|
||||||
|
num--; /* atomic_add is an add/fetch operation */
|
||||||
|
|
||||||
|
HT_INIT_KEY(&key);
|
||||||
|
|
||||||
|
for (iter = 0; iter < TEST_THREAD_ITERATIONS; iter++) {
|
||||||
|
if (!RAND_bytes((unsigned char *)&index, sizeof(unsigned int))) {
|
||||||
|
worker_exits[num] = "Failed to get random index";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
index %= TEST_MT_POOL_SZ;
|
||||||
|
if (!RAND_bytes((unsigned char *)&behavior, sizeof(char))) {
|
||||||
|
worker_exits[num] = "Failed to get random behavior";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
behavior &= BEHAVIOR_MASK;
|
||||||
|
|
||||||
|
expected_m = &test_mt_entries[index];
|
||||||
|
HT_KEY_RESET(&key);
|
||||||
|
HT_SET_KEY_FIELD(&key, index, index);
|
||||||
|
|
||||||
|
if (!CRYPTO_atomic_add(&global_iteration, 1, &giter, worker_lock)) {
|
||||||
|
worker_exits[num] = "Unable to increment global iterator";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch(behavior) {
|
||||||
|
case DO_LOOKUP:
|
||||||
|
ossl_ht_read_lock(m_ht);
|
||||||
|
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 (worker_exits[num] != NULL)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case DO_INSERT:
|
||||||
|
case DO_REPLACE:
|
||||||
|
ossl_ht_write_lock(m_ht);
|
||||||
|
if (behavior == DO_REPLACE) {
|
||||||
|
expected_rc = 1;
|
||||||
|
r = &m;
|
||||||
|
} else {
|
||||||
|
expected_rc = !expected_m->in_table;
|
||||||
|
r = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expected_rc != ossl_ht_mt_TEST_MT_ENTRY_insert(m_ht,
|
||||||
|
TO_HT_KEY(&key),
|
||||||
|
expected_m, r)) {
|
||||||
|
TEST_info("Iteration %d Expected rc %d on %s of element %d which is %s\n",
|
||||||
|
giter, expected_rc, behavior == DO_REPLACE ? "replace" : "insert",
|
||||||
|
index, expected_m->in_table ? "in table" : "not in table");
|
||||||
|
worker_exits[num] = "Failure on insert";
|
||||||
|
}
|
||||||
|
if (expected_rc == 1)
|
||||||
|
expected_m->in_table = 1;
|
||||||
|
ossl_ht_write_unlock(m_ht);
|
||||||
|
if (worker_exits[num] != NULL)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
case DO_DELETE:
|
||||||
|
ossl_ht_write_lock(m_ht);
|
||||||
|
expected_rc = expected_m->in_table;
|
||||||
|
if (expected_rc != ossl_ht_delete(m_ht, TO_HT_KEY(&key))) {
|
||||||
|
TEST_info("Iteration %d Expected rc %d on delete of element %d which is %s\n",
|
||||||
|
giter, expected_rc, index,
|
||||||
|
expected_m->in_table ? "in table" : "not in table");
|
||||||
|
worker_exits[num] = "Failure on delete";
|
||||||
|
}
|
||||||
|
if (expected_rc == 1) {
|
||||||
|
expected_m->in_table = 0;
|
||||||
|
CRYPTO_atomic_add(&expected_m->pending_delete, 1, &ret, worker_lock);
|
||||||
|
}
|
||||||
|
ossl_ht_write_unlock(m_ht);
|
||||||
|
if (worker_exits[num] != NULL)
|
||||||
|
return;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
worker_exits[num] = "Undefined behavior specified";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int test_hashtable_multithread(void)
|
||||||
|
{
|
||||||
|
HT_CONFIG hash_conf = {
|
||||||
|
NULL, /* use default context */
|
||||||
|
hashtable_mt_free, /* our free function */
|
||||||
|
NULL, /* default hash function */
|
||||||
|
0, /* default hash size */
|
||||||
|
};
|
||||||
|
int ret = 0;
|
||||||
|
thread_t workers[4];
|
||||||
|
int i;
|
||||||
|
#ifdef MEASURE_HASH_PERFORMANCE
|
||||||
|
struct timeval start, end, delta;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
memset(worker_exits, 0, sizeof(char *) * 4);
|
||||||
|
memset(test_mt_entries, 0, sizeof(TEST_MT_ENTRY) * TEST_MT_POOL_SZ);
|
||||||
|
memset(workers, 0, sizeof(thread_t) * 4);
|
||||||
|
|
||||||
|
m_ht = ossl_ht_new(&hash_conf);
|
||||||
|
|
||||||
|
if (!TEST_ptr(m_ht))
|
||||||
|
goto end;
|
||||||
|
|
||||||
|
worker_lock = CRYPTO_THREAD_lock_new();
|
||||||
|
if (worker_lock == NULL)
|
||||||
|
goto end_free;
|
||||||
|
#ifdef MEASURE_HASH_PERFORMANCE
|
||||||
|
gettimeofday(&start, NULL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
if (!run_thread(&workers[i], do_mt_hash_work))
|
||||||
|
goto shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
shutdown:
|
||||||
|
for (--i; i >= 0; i--) {
|
||||||
|
wait_for_thread(workers[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now that the workers are done, check for any error
|
||||||
|
* conditions
|
||||||
|
*/
|
||||||
|
ret = 1;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
if (worker_exits[i] != NULL) {
|
||||||
|
TEST_info("Worker %d failed: %s\n", i, worker_exits[i]);
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (free_failure == 1) {
|
||||||
|
TEST_info("Encountered a free failure");
|
||||||
|
ret = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef MEASURE_HASH_PERFORMANCE
|
||||||
|
gettimeofday(&end, NULL);
|
||||||
|
timeval_subtract(&delta, &end, &start);
|
||||||
|
TEST_info("multithread stress runs 40000 ops in %ld.%ld seconds", delta.tv_sec, delta.tv_usec);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
end_free:
|
||||||
|
shutting_down = 1;
|
||||||
|
ossl_ht_free(m_ht);
|
||||||
|
end:
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
int setup_tests(void)
|
int setup_tests(void)
|
||||||
{
|
{
|
||||||
ADD_TEST(test_int_lhash);
|
ADD_TEST(test_int_lhash);
|
||||||
ADD_TEST(test_stress);
|
ADD_TEST(test_stress);
|
||||||
ADD_TEST(test_int_hashtable);
|
ADD_TEST(test_int_hashtable);
|
||||||
ADD_TEST(test_hashtable_stress);
|
ADD_TEST(test_hashtable_stress);
|
||||||
|
ADD_TEST(test_hashtable_multithread);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue