781 lines
20 KiB
C
781 lines
20 KiB
C
![]() |
/*
|
||
|
* Copyright (c) 2018-2021 Apple Inc. All rights reserved.
|
||
|
*
|
||
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
||
|
*
|
||
|
* This file contains Original Code and/or Modifications of Original Code
|
||
|
* as defined in and that are subject to the Apple Public Source License
|
||
|
* Version 2.0 (the 'License'). You may not use this file except in
|
||
|
* compliance with the License. The rights granted to you under the License
|
||
|
* may not be used to create, or enable the creation or redistribution of,
|
||
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
||
|
* circumvent, violate, or enable the circumvention or violation of, any
|
||
|
* terms of an Apple operating system software license agreement.
|
||
|
*
|
||
|
* Please obtain a copy of the License at
|
||
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
||
|
*
|
||
|
* The Original Code and all software distributed under the License are
|
||
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
||
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
||
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
||
|
* Please see the License for the specific language governing rights and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
||
|
*/
|
||
|
|
||
|
#if (DEVELOPMENT || DEBUG)
|
||
|
|
||
|
#pragma clang optimize off
|
||
|
|
||
|
#include <libkern/OSAtomic.h>
|
||
|
#include <os/refcnt.h>
|
||
|
#include <skywalk/os_skywalk_private.h>
|
||
|
#include <skywalk/lib/cuckoo_hashtable.h>
|
||
|
|
||
|
#define CUCKOO_TEST_TAG "com.apple.skywalk.libcuckoo.test"
|
||
|
SKMEM_TAG_DEFINE(cuckoo_test_tag, CUCKOO_TEST_TAG);
|
||
|
|
||
|
os_refgrp_decl(static, cht_obj_refgrp, "CuckooTestRefGroup", NULL);
|
||
|
|
||
|
static void cuckoo_test_start(void *, wait_result_t);
|
||
|
static void cuckoo_test_stop(void *, wait_result_t);
|
||
|
|
||
|
extern unsigned int ml_wait_max_cpus(void);
|
||
|
|
||
|
// threading related
|
||
|
static int cht_inited = 0;
|
||
|
static int cht_enabled;
|
||
|
static int cht_busy;
|
||
|
|
||
|
decl_lck_mtx_data(static, cht_lock);
|
||
|
|
||
|
static struct cuckoo_hashtable *h = NULL;
|
||
|
|
||
|
struct cht_thread_conf {
|
||
|
thread_t ctc_thread; /* thread instance */
|
||
|
uint32_t ctc_nthreads; /* number of threads */
|
||
|
uint32_t ctc_id; /* thread id */
|
||
|
} __attribute__((aligned(CHANNEL_CACHE_ALIGN_MAX)));
|
||
|
|
||
|
static struct cht_thread_conf *chth_confs;
|
||
|
static uint32_t chth_nthreads;
|
||
|
static uint32_t chth_cnt;
|
||
|
static boolean_t chth_run;
|
||
|
|
||
|
enum {
|
||
|
COS_NOT_ADDED = 0, /* no inserted, available for insertion */
|
||
|
COS_BUSY = -1, /* being inserted/deleted */
|
||
|
COS_ADDED = 1, /* inserted, available for deletion */
|
||
|
} co_state_t;
|
||
|
|
||
|
// Cuckoo hashtable key object
|
||
|
|
||
|
struct cht_obj {
|
||
|
struct cuckoo_node co_cnode; // cuckoo node
|
||
|
int64_t co_key; // unique key
|
||
|
uint32_t co_hash; // dummy hash value (not collision-free)
|
||
|
os_refcnt_t co_refcnt; // reference count
|
||
|
volatile int32_t co_state; // co_state_t
|
||
|
uint32_t co_seen; // number of times seen
|
||
|
};
|
||
|
|
||
|
#if XNU_PLATFORM_WatchOS
|
||
|
static const uint32_t CHT_OBJ_MAX = 16 * 1024;
|
||
|
#else /* XNU_PLATFORM_WatchOS */
|
||
|
static const uint32_t CHT_OBJ_MAX = 512 * 1024;
|
||
|
#endif /* !XNU_PLATFORM_WatchOS */
|
||
|
static struct cht_obj *cht_objs;
|
||
|
|
||
|
static int
|
||
|
cht_obj_cmp__(struct cuckoo_node *node, void *key)
|
||
|
{
|
||
|
struct cht_obj *co = container_of(node, struct cht_obj, co_cnode);
|
||
|
int64_t key1 = *(int64_t *)key;
|
||
|
|
||
|
if (co->co_key < key1) {
|
||
|
return -1;
|
||
|
} else if (co->co_key > key1) {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_retain(struct cht_obj *co)
|
||
|
{
|
||
|
(void)os_ref_retain(&co->co_refcnt);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_retain__(struct cuckoo_node *node)
|
||
|
{
|
||
|
struct cht_obj *co = container_of(node, struct cht_obj, co_cnode);
|
||
|
return cht_obj_retain(co);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_release(struct cht_obj *co)
|
||
|
{
|
||
|
(void)os_ref_release(&co->co_refcnt);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_release__(struct cuckoo_node *node)
|
||
|
{
|
||
|
struct cht_obj *co = container_of(node, struct cht_obj, co_cnode);
|
||
|
cht_obj_release(co);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
cht_obj_refcnt(struct cht_obj *co)
|
||
|
{
|
||
|
return os_ref_get_count(&co->co_refcnt);
|
||
|
}
|
||
|
|
||
|
static struct cuckoo_hashtable_params params_template = {
|
||
|
.cht_capacity = 1024,
|
||
|
.cht_obj_cmp = cht_obj_cmp__,
|
||
|
.cht_obj_retain = cht_obj_retain__,
|
||
|
.cht_obj_release = cht_obj_release__,
|
||
|
};
|
||
|
|
||
|
void
|
||
|
cht_test_init(void)
|
||
|
{
|
||
|
if (OSCompareAndSwap(0, 1, &cht_inited)) {
|
||
|
lck_mtx_init(&cht_lock, &sk_lock_group, &sk_lock_attr);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void
|
||
|
cht_test_fini(void)
|
||
|
{
|
||
|
lck_mtx_destroy(&cht_lock, &sk_lock_group);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_init()
|
||
|
{
|
||
|
// init testing objects
|
||
|
cht_objs = sk_alloc_type_array(struct cht_obj, CHT_OBJ_MAX,
|
||
|
Z_WAITOK, cuckoo_test_tag);
|
||
|
VERIFY(cht_objs != NULL);
|
||
|
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
cht_objs[i].co_key = i;
|
||
|
do {
|
||
|
read_random(&cht_objs[i].co_hash, sizeof(cht_objs[i].co_hash));
|
||
|
} while (cht_objs[i].co_hash == 0);
|
||
|
os_ref_init(&cht_objs[i].co_refcnt, &cht_obj_refgrp);
|
||
|
cht_objs[i].co_state = COS_NOT_ADDED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_obj_fini()
|
||
|
{
|
||
|
VERIFY(cht_objs != NULL);
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
ASSERT(os_ref_release(&cht_objs[i].co_refcnt) == 0);
|
||
|
cht_objs[i].co_state = COS_NOT_ADDED;
|
||
|
cht_objs[i].co_seen = 0;
|
||
|
}
|
||
|
// init testing objects
|
||
|
sk_free_type_array(struct cht_obj, CHT_OBJ_MAX, cht_objs);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_basic_tests(void)
|
||
|
{
|
||
|
SK_ERR("start");
|
||
|
|
||
|
// Cuckoo hashtable creation
|
||
|
h = cuckoo_hashtable_create(¶ms_template);
|
||
|
|
||
|
// basic add/del
|
||
|
struct cht_obj co1 = {
|
||
|
.co_cnode = {NULL},
|
||
|
.co_key = -1,
|
||
|
.co_hash = 1,
|
||
|
.co_state = COS_NOT_ADDED,
|
||
|
.co_seen = 0
|
||
|
};
|
||
|
struct cht_obj co2 = {
|
||
|
.co_cnode = {NULL},
|
||
|
.co_key = -2,
|
||
|
.co_hash = 1,
|
||
|
.co_state = COS_NOT_ADDED,
|
||
|
.co_seen = 0
|
||
|
};
|
||
|
os_ref_init(&co1.co_refcnt, &cht_obj_refgrp);
|
||
|
os_ref_init(&co2.co_refcnt, &cht_obj_refgrp);
|
||
|
|
||
|
struct cuckoo_node *node = NULL;
|
||
|
__block struct cht_obj *co = NULL;
|
||
|
int error = 0;
|
||
|
|
||
|
// add objs with duplicate hash
|
||
|
error = cuckoo_hashtable_add_with_hash(h, &co1.co_cnode, co1.co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
|
||
|
error = cuckoo_hashtable_add_with_hash(h, &co2.co_cnode, co2.co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == 2);
|
||
|
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co1.co_key, co1.co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co1.co_cnode);
|
||
|
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co2.co_key, co2.co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co2.co_cnode);
|
||
|
|
||
|
cuckoo_hashtable_del(h, &co1.co_cnode, co1.co_hash);
|
||
|
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co1.co_key, co1.co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co2.co_key, co2.co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co2.co_cnode);
|
||
|
|
||
|
cuckoo_hashtable_del(h, &co2.co_cnode, co2.co_hash);
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co2.co_key, co2.co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == 0);
|
||
|
|
||
|
// add all objs
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
error = cuckoo_hashtable_add_with_hash(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == i + 1);
|
||
|
co->co_state = COS_ADDED;
|
||
|
}
|
||
|
|
||
|
// find all objs
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
ASSERT(co->co_state = COS_ADDED);
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co->co_cnode);
|
||
|
ASSERT(cht_obj_refcnt(co) == 3);
|
||
|
cht_obj_release(co);
|
||
|
}
|
||
|
|
||
|
// walk all objs
|
||
|
cuckoo_hashtable_foreach(h, ^(struct cuckoo_node *curr_node, uint32_t curr_hash) {
|
||
|
co = container_of(curr_node, struct cht_obj, co_cnode);
|
||
|
ASSERT(co->co_hash == curr_hash);
|
||
|
ASSERT(cht_obj_refcnt(co) == 2);
|
||
|
co->co_seen++;
|
||
|
});
|
||
|
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
ASSERT(co->co_seen == 1);
|
||
|
}
|
||
|
|
||
|
size_t memory_use_before_shrink = cuckoo_hashtable_memory_footprint(h);
|
||
|
|
||
|
// del all objs
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
ASSERT(co->co_state = COS_ADDED);
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(cht_obj_refcnt(co) == 3);
|
||
|
cuckoo_hashtable_del(h, &co->co_cnode, co->co_hash);
|
||
|
cht_obj_release(co);
|
||
|
ASSERT(cht_obj_refcnt(co) == 1);
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == CHT_OBJ_MAX - i - 1);
|
||
|
co->co_seen = 0;
|
||
|
}
|
||
|
|
||
|
// shrink
|
||
|
cuckoo_hashtable_try_shrink(h);
|
||
|
|
||
|
ASSERT(cuckoo_hashtable_memory_footprint(h) < memory_use_before_shrink);
|
||
|
|
||
|
// self healthy check
|
||
|
cuckoo_hashtable_health_check(h);
|
||
|
|
||
|
cuckoo_hashtable_free(h);
|
||
|
|
||
|
SK_ERR("done");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_ops_begin()
|
||
|
{
|
||
|
/* let skmem_test_start() know we're ready */
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
os_atomic_inc(&chth_cnt, relaxed);
|
||
|
wakeup((caddr_t)&chth_cnt);
|
||
|
|
||
|
do {
|
||
|
(void) msleep(&chth_run, &cht_lock, (PZERO - 1),
|
||
|
"chthfuncw", NULL);
|
||
|
} while (!chth_run);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_ops_done()
|
||
|
{
|
||
|
/* let skmem_test_start() know we're finished */
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
VERIFY(os_atomic_dec_orig(&chth_cnt, relaxed) != 0);
|
||
|
wakeup((caddr_t)&chth_cnt);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_add_init(void)
|
||
|
{
|
||
|
h = cuckoo_hashtable_create(¶ms_template);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_add(void *v, wait_result_t w)
|
||
|
{
|
||
|
#pragma unused(v, w)
|
||
|
cht_concurrent_ops_begin();
|
||
|
|
||
|
struct cht_thread_conf *conf = v;
|
||
|
uint32_t objs_per_cpu = CHT_OBJ_MAX / conf->ctc_nthreads;
|
||
|
uint32_t objs_start_idx = objs_per_cpu * conf->ctc_id;
|
||
|
uint32_t objs_to_add = objs_per_cpu;
|
||
|
|
||
|
// last thread id add any tailing objs
|
||
|
if (conf->ctc_id == conf->ctc_nthreads - 1) {
|
||
|
objs_to_add += (CHT_OBJ_MAX % conf->ctc_nthreads);
|
||
|
}
|
||
|
|
||
|
for (uint32_t i = 0; i < objs_to_add; i++) {
|
||
|
struct cht_obj *co = &cht_objs[objs_start_idx + i];
|
||
|
int error = cuckoo_hashtable_add_with_hash(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
co->co_state = COS_ADDED;
|
||
|
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co->co_cnode);
|
||
|
cht_obj_release(co);
|
||
|
}
|
||
|
|
||
|
cht_concurrent_ops_done();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_add_check(void)
|
||
|
{
|
||
|
__block struct cht_obj *co = NULL;
|
||
|
struct cuckoo_node *node = NULL;
|
||
|
|
||
|
// find all objs
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
ASSERT(co->co_state = COS_ADDED);
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co->co_cnode);
|
||
|
ASSERT(cht_obj_refcnt(co) == 3);
|
||
|
cht_obj_release(co);
|
||
|
}
|
||
|
|
||
|
// walk all objs
|
||
|
cuckoo_hashtable_foreach(h, ^(struct cuckoo_node *curr_node, uint32_t curr_hash) {
|
||
|
co = container_of(curr_node, struct cht_obj, co_cnode);
|
||
|
ASSERT(co->co_hash == curr_hash);
|
||
|
ASSERT(cht_obj_refcnt(co) == 2);
|
||
|
co->co_seen++;
|
||
|
});
|
||
|
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
//ASSERT(co->co_seen == 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_add_fini(void)
|
||
|
{
|
||
|
struct cht_obj *co = NULL;
|
||
|
struct cuckoo_node *node = NULL;
|
||
|
|
||
|
// del all objs
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
co = &cht_objs[i];
|
||
|
ASSERT(co->co_state = COS_ADDED);
|
||
|
node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(cht_obj_refcnt(co) == 3);
|
||
|
cuckoo_hashtable_del(h, &co->co_cnode, co->co_hash);
|
||
|
cht_obj_release(co);
|
||
|
ASSERT(cht_obj_refcnt(co) == 1);
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == CHT_OBJ_MAX - i - 1);
|
||
|
}
|
||
|
|
||
|
cuckoo_hashtable_free(h);
|
||
|
}
|
||
|
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_del_init(void)
|
||
|
{
|
||
|
h = cuckoo_hashtable_create(¶ms_template);
|
||
|
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
struct cht_obj *co = &cht_objs[i];
|
||
|
int error = cuckoo_hashtable_add_with_hash(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == i + 1);
|
||
|
co->co_state = COS_ADDED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_del(void *v, wait_result_t w)
|
||
|
{
|
||
|
#pragma unused(v, w)
|
||
|
cht_concurrent_ops_begin();
|
||
|
|
||
|
struct cht_thread_conf *conf = v;
|
||
|
uint32_t objs_per_cpu = CHT_OBJ_MAX / conf->ctc_nthreads;
|
||
|
uint32_t objs_start_idx = objs_per_cpu * conf->ctc_id;
|
||
|
uint32_t objs_to_del = objs_per_cpu;
|
||
|
|
||
|
// last thread id add any tailing objs
|
||
|
if (conf->ctc_id == conf->ctc_nthreads - 1) {
|
||
|
objs_to_del += (CHT_OBJ_MAX % conf->ctc_nthreads);
|
||
|
}
|
||
|
|
||
|
for (uint32_t i = 0; i < objs_to_del; i++) {
|
||
|
struct cht_obj *co = &cht_objs[objs_start_idx + i];
|
||
|
int error = cuckoo_hashtable_del(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
co->co_state = COS_NOT_ADDED;
|
||
|
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
ASSERT(cht_obj_refcnt(co) == 1);
|
||
|
}
|
||
|
|
||
|
cht_concurrent_ops_done();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_del_check(void)
|
||
|
{
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == 0);
|
||
|
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
struct cht_obj *co = &cht_objs[i];
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
ASSERT(cht_obj_refcnt(co) == 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_del_fini(void)
|
||
|
{
|
||
|
cuckoo_hashtable_free(h);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_duo_init(void)
|
||
|
{
|
||
|
struct cuckoo_hashtable_params p = params_template;
|
||
|
p.cht_capacity = CHT_OBJ_MAX / 2;
|
||
|
h = cuckoo_hashtable_create(&p);
|
||
|
|
||
|
// populate 1/3 of the objects
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i += 3) {
|
||
|
struct cht_obj *co = &cht_objs[i];
|
||
|
int error = cuckoo_hashtable_add_with_hash(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
co->co_state = COS_ADDED;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_duo(void *v, wait_result_t w)
|
||
|
{
|
||
|
#pragma unused(v, w)
|
||
|
#define DUO_ITERATIONS (2 * CHT_OBJ_MAX)
|
||
|
|
||
|
#define DUO_OPS_MASK 0x0000000f
|
||
|
#define DUO_OPS_ADD 0x9
|
||
|
|
||
|
#define DUO_IDX_MASK 0xfffffff0
|
||
|
#define DUO_IDX_SHIFT 0x8
|
||
|
|
||
|
cht_concurrent_ops_begin();
|
||
|
|
||
|
uint32_t *rands;
|
||
|
rands = sk_alloc_data(sizeof(uint32_t) * DUO_ITERATIONS, Z_WAITOK, cuckoo_test_tag);
|
||
|
VERIFY(rands != NULL);
|
||
|
read_random(rands, sizeof(uint32_t) * DUO_ITERATIONS);
|
||
|
|
||
|
for (uint32_t i = 0; i < DUO_ITERATIONS; i++) {
|
||
|
uint32_t rand, ops, idx;
|
||
|
rand = rands[i];
|
||
|
ops = rand & DUO_OPS_MASK;
|
||
|
idx = (rand >> DUO_IDX_SHIFT) % CHT_OBJ_MAX;
|
||
|
|
||
|
// choose an ops (add, del, shrink)
|
||
|
if (ops < DUO_OPS_ADD) {
|
||
|
struct cht_obj *co = &cht_objs[idx];
|
||
|
if (os_atomic_cmpxchg(&co->co_state, COS_NOT_ADDED, COS_BUSY, acq_rel)) {
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
int error = cuckoo_hashtable_add_with_hash(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
ASSERT(cht_obj_refcnt(co) == 2);
|
||
|
|
||
|
co->co_state = COS_ADDED;
|
||
|
}
|
||
|
} else {
|
||
|
struct cht_obj *co = &cht_objs[idx];
|
||
|
if (os_atomic_cmpxchg(&co->co_state, COS_ADDED, COS_BUSY, acq_rel)) {
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co->co_cnode);
|
||
|
int error = cuckoo_hashtable_del(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
ASSERT(cht_obj_refcnt(co) == 2);
|
||
|
cht_obj_release(co);
|
||
|
|
||
|
co->co_state = COS_NOT_ADDED;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
sk_free_data(rands, sizeof(uint32_t) * DUO_ITERATIONS);
|
||
|
cht_concurrent_ops_done();
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_duo_check(void)
|
||
|
{
|
||
|
size_t added = 0;
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
struct cht_obj *co = &cht_objs[i];
|
||
|
if (co->co_state == COS_ADDED) {
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node != NULL);
|
||
|
ASSERT(node == &co->co_cnode);
|
||
|
added++;
|
||
|
cht_obj_release(co);
|
||
|
} else {
|
||
|
struct cuckoo_node *node = cuckoo_hashtable_find_with_hash(h, &co->co_key, co->co_hash);
|
||
|
ASSERT(node == NULL);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT(added == cuckoo_hashtable_entries(h));
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_duo_fini(void)
|
||
|
{
|
||
|
for (uint32_t i = 0; i < CHT_OBJ_MAX; i++) {
|
||
|
struct cht_obj *co = &cht_objs[i];
|
||
|
if (co->co_state == COS_ADDED) {
|
||
|
int error = cuckoo_hashtable_del(h, &co->co_cnode, co->co_hash);
|
||
|
ASSERT(error == 0);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ASSERT(cuckoo_hashtable_entries(h) == 0);
|
||
|
|
||
|
cuckoo_hashtable_free(h);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cht_concurrent_tests(
|
||
|
void (*cht_concurrent_init)(void),
|
||
|
void (*cht_concurrent_ops)(void *v, wait_result_t w),
|
||
|
void (*cht_concurrent_check)(void),
|
||
|
void (*cht_concurrent_fini)(void))
|
||
|
{
|
||
|
uint32_t nthreads = MAX(2, ml_wait_max_cpus() * 3 / 4);
|
||
|
|
||
|
SK_ERR("start, nthreads %d", nthreads);
|
||
|
|
||
|
cht_concurrent_init();
|
||
|
|
||
|
// init multithread test config
|
||
|
if (chth_confs == NULL) {
|
||
|
chth_nthreads = nthreads;
|
||
|
chth_confs = sk_alloc_type_array(struct cht_thread_conf, nthreads,
|
||
|
Z_WAITOK | Z_NOFAIL, cuckoo_test_tag);
|
||
|
}
|
||
|
|
||
|
for (uint32_t i = 0; i < nthreads; i++) {
|
||
|
chth_confs[i].ctc_nthreads = nthreads;
|
||
|
chth_confs[i].ctc_id = i;
|
||
|
if (kernel_thread_start(cht_concurrent_ops, (void *)&chth_confs[i],
|
||
|
&chth_confs[i].ctc_thread) != KERN_SUCCESS) {
|
||
|
panic("failed to create cuckoo test thread");
|
||
|
__builtin_unreachable();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// wait for threads to spwan
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
do {
|
||
|
struct timespec ts = { 0, 100 * USEC_PER_SEC };
|
||
|
(void) msleep(&chth_cnt, &cht_lock, (PZERO - 1),
|
||
|
"skmtstartw", &ts);
|
||
|
} while (chth_cnt < nthreads);
|
||
|
VERIFY(chth_cnt == nthreads);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
|
||
|
// signal threads to run
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
VERIFY(!chth_run);
|
||
|
chth_run = TRUE;
|
||
|
wakeup((caddr_t)&chth_run);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
|
||
|
// wait until all threads are done
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
do {
|
||
|
struct timespec ts = { 0, 100 * USEC_PER_SEC };
|
||
|
(void) msleep(&chth_cnt, &cht_lock, (PZERO - 1),
|
||
|
"skmtstopw", &ts);
|
||
|
} while (chth_cnt != 0);
|
||
|
chth_run = FALSE;
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
|
||
|
// check results
|
||
|
cht_concurrent_check();
|
||
|
|
||
|
cht_concurrent_fini();
|
||
|
|
||
|
SK_ERR("done");
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cuckoo_test_start(void *v, wait_result_t w)
|
||
|
{
|
||
|
#pragma unused(v, w)
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
VERIFY(!cht_busy);
|
||
|
cht_busy = 1;
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
|
||
|
cht_obj_init();
|
||
|
|
||
|
cht_basic_tests();
|
||
|
|
||
|
cht_concurrent_tests(cht_concurrent_add_init, cht_concurrent_add, cht_concurrent_add_check, cht_concurrent_add_fini);
|
||
|
cht_concurrent_tests(cht_concurrent_del_init, cht_concurrent_del, cht_concurrent_del_check, cht_concurrent_del_fini);
|
||
|
cht_concurrent_tests(cht_concurrent_duo_init, cht_concurrent_duo, cht_concurrent_duo_check, cht_concurrent_duo_fini);
|
||
|
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
cht_enabled = 1;
|
||
|
wakeup((caddr_t)&cht_enabled);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
cuckoo_test_stop(void *v, wait_result_t w)
|
||
|
{
|
||
|
#pragma unused(v, w)
|
||
|
|
||
|
if (chth_confs != NULL) {
|
||
|
sk_free_type_array(struct cht_thread_conf, chth_nthreads, chth_confs);
|
||
|
chth_confs = NULL;
|
||
|
chth_nthreads = 0;
|
||
|
}
|
||
|
|
||
|
cht_obj_fini();
|
||
|
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
VERIFY(cht_busy);
|
||
|
cht_busy = 0;
|
||
|
cht_enabled = 0;
|
||
|
wakeup((caddr_t)&cht_enabled);
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
sysctl_cuckoo_test(__unused struct sysctl_oid *oidp,
|
||
|
__unused void *arg1, __unused int arg2, struct sysctl_req *req)
|
||
|
{
|
||
|
int error, newvalue, changed;
|
||
|
thread_t th;
|
||
|
thread_continue_t func;
|
||
|
|
||
|
lck_mtx_lock(&cht_lock);
|
||
|
if ((error = sysctl_io_number(req, cht_enabled, sizeof(int),
|
||
|
&newvalue, &changed)) != 0) {
|
||
|
SK_ERR("failed to get new sysctl value");
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (changed && cht_enabled != newvalue) {
|
||
|
if (newvalue && cht_busy) {
|
||
|
SK_ERR("previous cuckoo test instance is still active");
|
||
|
error = EBUSY;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
if (newvalue) {
|
||
|
func = cuckoo_test_start;
|
||
|
} else {
|
||
|
func = cuckoo_test_stop;
|
||
|
}
|
||
|
|
||
|
if (kernel_thread_start(func, NULL, &th) != KERN_SUCCESS) {
|
||
|
SK_ERR("failed to create cuckoo test action thread");
|
||
|
error = EBUSY;
|
||
|
goto done;
|
||
|
}
|
||
|
do {
|
||
|
SK_ERR("waiting for %s to complete",
|
||
|
newvalue ? "startup" : "shutdown");
|
||
|
error = msleep(&cht_enabled, &cht_lock,
|
||
|
PWAIT | PCATCH, "skmtw", NULL);
|
||
|
/* BEGIN CSTYLED */
|
||
|
/*
|
||
|
* Loop exit conditions:
|
||
|
* - we were interrupted
|
||
|
* OR
|
||
|
* - we are starting up and are enabled
|
||
|
* (Startup complete)
|
||
|
* OR
|
||
|
* - we are starting up and are not busy
|
||
|
* (Failed startup)
|
||
|
* OR
|
||
|
* - we are shutting down and are not busy
|
||
|
* (Shutdown complete)
|
||
|
*/
|
||
|
/* END CSTYLED */
|
||
|
} while (!((error == EINTR) || (newvalue && cht_enabled) ||
|
||
|
(newvalue && !cht_busy) || (!newvalue && !cht_busy)));
|
||
|
|
||
|
SK_ERR("exited from msleep");
|
||
|
thread_deallocate(th);
|
||
|
}
|
||
|
|
||
|
done:
|
||
|
lck_mtx_unlock(&cht_lock);
|
||
|
return error;
|
||
|
}
|
||
|
|
||
|
SYSCTL_PROC(_kern_skywalk_libcuckoo, OID_AUTO, test,
|
||
|
CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_LOCKED, NULL, 0,
|
||
|
sysctl_cuckoo_test, "I", "Start Cuckoo hashtable test");
|
||
|
|
||
|
#endif /* DEVELOPMENT || DEBUG */
|