Files
bind9/tests/dns/qpmulti_test.c
Ondřej Surý f5c204ac3e Move the library init and shutdown to executables
Instead of relying on unreliable order of execution of the library
constructors and destructors, move them to individual binaries.  The
advantage is that the execution time and order will remain constant and
will not depend on the dynamic load dependency solver.

This requires more work, but that was mitigated by a simple requirement,
any executable using libisc and libdns, must include <isc/lib.h> and
<dns/lib.h> respectively (in this particular order).  In turn, these two
headers must not be included from within any library as they contain
inlined functions marked with constructor/destructor attributes.
2025-02-22 16:19:00 +01:00

386 lines
9.0 KiB
C

/*
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
*
* SPDX-License-Identifier: MPL-2.0
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/.
*
* See the COPYRIGHT file distributed with this work for additional
* information regarding copyright ownership.
*/
#include <assert.h>
#include <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/assertions.h>
#include <isc/lib.h>
#include <isc/log.h>
#include <isc/loop.h>
#include <isc/magic.h>
#include <isc/mem.h>
#include <isc/random.h>
#include <isc/refcount.h>
#include <isc/rwlock.h>
#include <isc/urcu.h>
#include <isc/util.h>
#include <dns/lib.h>
#include <dns/qp.h>
#include <dns/types.h>
#include "qp_p.h"
#include <tests/isc.h>
#include <tests/qp.h>
#define VERBOSE 0
#define ITEM_COUNT 12345
#define TRANSACTION_SIZE 123
#define TRANSACTION_COUNT 1234
#if VERBOSE
#define TRACE(fmt, ...) \
isc_log_write(DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_QP, \
ISC_LOG_DEBUG(7), "%s:%d:%s(): " fmt, __FILE__, \
__LINE__, __func__, ##__VA_ARGS__)
#else
#define TRACE(...)
#endif
#if VERBOSE
#define ASSERT(p) \
if (!(p)) { \
TRACE("%s failed", #p); \
ok = false; \
} else
#else
#define ASSERT(p) assert_true(p)
#endif
static void
setup_logging(void) {
#if VERBOSE
isc_log_setdebuglevel(7);
#endif
isc_logconfig_t *logconfig = isc_logconfig_get();
isc_log_createandusechannel(
logconfig, "default_stderr", ISC_LOG_TOFILEDESC,
ISC_LOG_DYNAMIC, ISC_LOGDESTINATION_STDERR,
ISC_LOG_PRINTPREFIX | ISC_LOG_PRINTTIME | ISC_LOG_ISO8601,
ISC_LOGCATEGORY_DEFAULT, ISC_LOGMODULE_DEFAULT);
}
static struct {
uint32_t refcount;
bool in_ro;
bool in_rw;
uint8_t len;
dns_qpkey_t key;
dns_qpkey_t ascii;
} item[ITEM_COUNT];
static void
item_attach(void *ctx, void *pval, uint32_t ival) {
INSIST(ctx == NULL);
INSIST(pval == &item[ival]);
item[ival].refcount++;
}
static void
item_detach(void *ctx, void *pval, uint32_t ival) {
assert_null(ctx);
assert_ptr_equal(pval, &item[ival]);
assert_int_not_equal(item[ival].refcount, 0);
item[ival].refcount--;
}
static size_t
item_makekey(dns_qpkey_t key, void *ctx, void *pval, uint32_t ival) {
INSIST(ctx == NULL);
uintptr_t ip = (uintptr_t)pval;
uintptr_t lo = (uintptr_t)item;
uintptr_t hi = sizeof(item) + lo;
if (!(ival < ARRAY_SIZE(item) && lo <= ip && ip < hi &&
pval == &item[ival]))
{
ISC_INSIST(ival < ARRAY_SIZE(item));
ISC_INSIST(pval != NULL);
ISC_INSIST(ip >= lo);
ISC_INSIST(ip < hi);
ISC_INSIST(pval == &item[ival]);
}
memmove(key, item[ival].key, item[ival].len);
return item[ival].len;
}
static void
testname(void *ctx, char *buf, size_t size) {
REQUIRE(ctx == NULL);
strlcpy(buf, "test", size);
}
const dns_qpmethods_t test_methods = {
item_attach,
item_detach,
item_makekey,
testname,
};
static uint8_t
random_byte(void) {
return isc_random_uniform(SHIFT_OFFSET - SHIFT_NOBYTE) + SHIFT_NOBYTE;
}
static void
setup_items(void) {
void *pval = NULL;
dns_qp_t *qp = NULL;
dns_qp_create(mctx, &test_methods, NULL, &qp);
for (size_t i = 0; i < ARRAY_SIZE(item); i++) {
do {
size_t len = isc_random_uniform(16) + 4;
item[i].len = len;
for (size_t off = 0; off < len; off++) {
item[i].key[off] = random_byte();
}
memmove(item[i].ascii, item[i].key, len);
qp_test_keytoascii(item[i].ascii, len);
} while (dns_qp_getkey(qp, item[i].key, item[i].len, &pval,
NULL) == ISC_R_SUCCESS);
assert_int_equal(dns_qp_insert(qp, &item[i], i), ISC_R_SUCCESS);
}
dns_qp_destroy(&qp);
}
static bool
checkkey(dns_qpreadable_t qpr, size_t i, bool exists, const char *rubric) {
bool ok = true;
void *pval = NULL;
uint32_t ival = ~0U;
isc_result_t result;
result = dns_qp_getkey(qpr, item[i].key, item[i].len, &pval, &ival);
if (result == ISC_R_SUCCESS) {
assert_true(exists);
assert_ptr_equal(pval, &item[i]);
assert_int_equal(ival, i);
} else if (result == ISC_R_NOTFOUND) {
assert_false(exists);
assert_null(pval);
assert_int_equal(ival, ~0U);
} else {
UNREACHABLE();
}
if (!ok) {
TRACE("checkkey %p %zu %s %s %s", qpr.qpr, i,
exists ? "exists" : "missing", isc_result_totext(result),
rubric);
UNUSED(rubric);
}
return ok;
}
static bool
checkallro(dns_qpreadable_t qp) {
bool ok = true;
for (size_t i = 0; i < ARRAY_SIZE(item); i++) {
ASSERT(checkkey(qp, i, item[i].in_ro, "checkall ro"));
}
if (!ok) {
qp_test_dumptrie(qp);
TRACE("checkallro failed");
}
return ok;
}
static bool
checkallrw(dns_qpreadable_t qp) {
bool ok = true;
for (size_t i = 0; i < ARRAY_SIZE(item); i++) {
ASSERT(checkkey(qp, i, item[i].in_rw, "checkall rw"));
}
if (!ok) {
qp_test_dumptrie(qp);
TRACE("checkallrw failed");
}
return ok;
}
static void
one_transaction(dns_qpmulti_t *qpm) {
isc_result_t result;
bool ok = true;
dns_qpreader_t *qpo = NULL;
dns_qpsnap_t *qps = NULL;
dns_qpread_t qpr = { 0 };
dns_qp_t *qpw = NULL;
bool snap = isc_random_uniform(2) == 0;
bool update = isc_random_uniform(2) != 0;
bool rollback = update && isc_random_uniform(4) == 0;
size_t count = isc_random_uniform(TRANSACTION_SIZE);
TRACE("transaction %s %s %s size %zu", snap ? "snapshot" : "query",
update ? "update" : "write", rollback ? "rollback" : "commit",
count);
/*
* We need to take care to avoid lock order inversion:
* The write mutex must be the outermost lock if it is held.
* The mutex must not be taken while the rwlock is held.
*/
/* briefly take and drop mutex */
if (snap) {
dns_qpmulti_snapshot(qpm, &qps);
qpo = (dns_qpreader_t *)qps;
}
/* take mutex */
if (update) {
dns_qpmulti_update(qpm, &qpw);
} else {
dns_qpmulti_write(qpm, &qpw);
}
if (!snap) {
dns_qpmulti_query(qpm, &qpr);
qpo = (dns_qpreader_t *)&qpr;
}
for (size_t n = 0; n < count; n++) {
size_t i = isc_random_uniform(ARRAY_SIZE(item));
ASSERT(checkkey(qpo, i, item[i].in_ro, "before ro"));
ASSERT(checkkey(qpw, i, item[i].in_rw, "before rw"));
if (item[i].in_rw) {
/* TRACE("delete %zu %.*s", i,
item[i].len, item[i].ascii); */
void *pvald = NULL;
uint32_t ivald = 0;
result = dns_qp_deletekey(qpw, item[i].key, item[i].len,
&pvald, &ivald);
ASSERT(result == ISC_R_SUCCESS);
ASSERT(pvald == &item[i]);
ASSERT(ivald == i);
item[i].in_rw = false;
} else {
/* TRACE("insert %zu %.*s", i,
item[i].len, item[i].ascii); */
result = dns_qp_insert(qpw, &item[i], i);
ASSERT(result == ISC_R_SUCCESS);
item[i].in_rw = true;
}
ASSERT(checkkey(qpo, i, item[i].in_ro, "after ro"));
ASSERT(checkkey(qpw, i, item[i].in_rw, "after rw"));
if (!ok) {
TRACE("mutate %zu/%zu failed", n, count);
qp_test_dumptrie(qpo);
qp_test_dumptrie(qpw);
}
assert_true(ok);
}
assert_true(checkallro(qpo));
assert_true(checkallrw(qpw));
if (!snap) {
dns_qpread_destroy(qpm, &qpr);
}
if (rollback) {
TRACE("transaction rollback");
dns_qpmulti_rollback(qpm, &qpw);
/* mutex is now dropped */
dns_qpmulti_query(qpm, &qpr);
for (size_t i = 0; i < ARRAY_SIZE(item); i++) {
if (snap) {
ASSERT(checkkey(qps, i, item[i].in_ro,
"rollback ro"));
}
item[i].in_rw = item[i].in_ro;
ASSERT(checkkey(&qpr, i, item[i].in_rw, "rollback rw"));
}
dns_qpread_destroy(qpm, &qpr);
} else {
TRACE("transaction commit");
dns_qpmulti_commit(qpm, &qpw);
/* mutex is now dropped */
dns_qpmulti_query(qpm, &qpr);
for (size_t i = 0; i < ARRAY_SIZE(item); i++) {
if (snap) {
ASSERT(checkkey(qps, i, item[i].in_ro,
"commit ro"));
}
item[i].in_ro = item[i].in_rw;
ASSERT(checkkey(&qpr, i, item[i].in_rw, "commit rw"));
}
dns_qpread_destroy(qpm, &qpr);
}
if (snap) {
TRACE("snapshot destroy");
/* takes mutex briefly */
dns_qpsnap_destroy(qpm, &qps);
}
TRACE("completed %s %s %s size %zu", snap ? "snapshot" : "query",
update ? "update" : "write", rollback ? "rollback" : "commit",
count);
if (!ok) {
TRACE("transaction failed");
dns_qpmulti_query(qpm, &qpr);
qp_test_dumptrie(&qpr);
dns_qpread_destroy(qpm, &qpr);
}
assert_true(ok);
}
static void
many_transactions(void *arg) {
UNUSED(arg);
dns_qpmulti_t *qpm = NULL;
dns_qpmulti_create(mctx, &test_methods, NULL, &qpm);
qpm->writer.write_protect = true;
for (size_t n = 0; n < TRANSACTION_COUNT; n++) {
TRACE("transaction %zu", n);
one_transaction(qpm);
rcu_quiescent_state();
}
dns_qpmulti_destroy(&qpm);
isc_loopmgr_shutdown(loopmgr);
}
ISC_RUN_TEST_IMPL(qpmulti) {
setup_loopmgr(NULL);
setup_logging();
setup_items();
isc_loop_setup(isc_loop_main(loopmgr), many_transactions, NULL);
isc_loopmgr_run(loopmgr);
rcu_barrier();
isc_loopmgr_destroy(&loopmgr);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(qpmulti)
ISC_TEST_LIST_END
ISC_TEST_MAIN