/*
 * 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++) {
		os_ref_release_last(&cht_objs[i].co_refcnt);
		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(&params_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(&params_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(&params_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 */
