mirror of
https://github.com/openssl/openssl.git
synced 2025-04-12 20:30:52 +08:00
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
|
||||
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.
|
||||
|
||||
=over 2
|
||||
|
||||
=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
|
||||
contains configurations options for hashtable. Current config options consist
|
||||
of:
|
||||
I<ht_free_fn> The function to call to free a value, may be B<NULL>.
|
||||
I<ht_hash_fn> The function to generate a hash value for a key, 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 NULL.
|
||||
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
|
||||
@ -109,13 +109,13 @@ called to release the element data.
|
||||
|
||||
=item *
|
||||
|
||||
ossl_ht_insert() inserts an HT_VALUE element into the hash table, to be
|
||||
hashed using the corresponding HT_KEY value.
|
||||
ossl_ht_insert() inserts an B<HT_VALUE> element into the hash table, to be
|
||||
hashed using the corresponding B<HT_KEY> value.
|
||||
|
||||
=item *
|
||||
|
||||
ossl_ht_delete() deletes an entry from the hashtable indexed by the passed
|
||||
HT_KEY value.
|
||||
B<HT_KEY> value.
|
||||
|
||||
=item *
|
||||
|
||||
@ -138,20 +138,20 @@ properly synchronize such modifications with other threads.
|
||||
|
||||
ossl_ht_filter() iterates over all elements of the hash table, calling
|
||||
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.
|
||||
The completed list is returned to the caller as an HT_VALUE_LIST object
|
||||
corresponding B<HT_VALUE> is placed on a list, and its reference count incremented.
|
||||
The completed list is returned to the caller as an B<HT_VALUE_LIST> object
|
||||
|
||||
=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
|
||||
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
|
||||
ossl_ht_put on the HT_VALUE to prevent memory leaks.
|
||||
ossl_ht_put() on the B<HT_VALUE> to prevent memory leaks.
|
||||
|
||||
=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.
|
||||
|
||||
=item *
|
||||
@ -240,23 +240,23 @@ pointer
|
||||
|
||||
<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,
|
||||
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 *
|
||||
|
||||
<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
|
||||
|
||||
=item *
|
||||
|
||||
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 *
|
||||
|
||||
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
|
||||
|
||||
@ -264,7 +264,7 @@ Returns true if the HT_VALUE object passed in is of type <TYPE>
|
||||
|
||||
=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);
|
||||
|
||||
@ -275,10 +275,10 @@ key was not found.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
=head1 EXAMPLES
|
||||
|
@ -14,10 +14,12 @@
|
||||
#include <openssl/opensslconf.h>
|
||||
#include <openssl/lhash.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/crypto.h>
|
||||
#include <internal/hashtable.h>
|
||||
|
||||
#include "internal/nelem.h"
|
||||
#include "threadstest.h"
|
||||
#include "testutil.h"
|
||||
|
||||
/*
|
||||
@ -284,7 +286,8 @@ static int test_int_hashtable(void)
|
||||
todel = ossl_ht_delete(ht, TO_HT_KEY(&key));
|
||||
if (dels[i].should_del) {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
@ -464,11 +467,235 @@ end:
|
||||
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)
|
||||
{
|
||||
ADD_TEST(test_int_lhash);
|
||||
ADD_TEST(test_stress);
|
||||
ADD_TEST(test_int_hashtable);
|
||||
ADD_TEST(test_hashtable_stress);
|
||||
ADD_TEST(test_hashtable_multithread);
|
||||
return 1;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user