1
0
Fork 0
bind9/tests/isc/quota_test.c
Daniel Baumann f66ff7eae6
Adding upstream version 1:9.20.9.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
2025-06-21 13:32:37 +02:00

317 lines
7.6 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 <inttypes.h>
#include <sched.h> /* IWYU pragma: keep */
#include <setjmp.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define UNIT_TESTING
#include <cmocka.h>
#include <isc/job.h>
#include <isc/quota.h>
#include <isc/result.h>
#include <isc/thread.h>
#include <isc/util.h>
#include <isc/uv.h>
#include <tests/isc.h>
isc_quota_t quota;
ISC_RUN_TEST_IMPL(isc_quota_get_set) {
UNUSED(state);
isc_quota_init(&quota, 100);
assert_int_equal(isc_quota_getmax(&quota), 100);
assert_int_equal(isc_quota_getsoft(&quota), 0);
isc_quota_max(&quota, 50);
isc_quota_soft(&quota, 30);
assert_int_equal(isc_quota_getmax(&quota), 50);
assert_int_equal(isc_quota_getsoft(&quota), 30);
assert_int_equal(isc_quota_getused(&quota), 0);
isc_quota_acquire(&quota);
assert_int_equal(isc_quota_getused(&quota), 1);
isc_quota_release(&quota);
assert_int_equal(isc_quota_getused(&quota), 0);
/* Unlimited */
isc_quota_max(&quota, 0);
isc_quota_soft(&quota, 0);
isc_quota_destroy(&quota);
}
static void
add_quota(isc_quota_t *source, isc_result_t expected_result,
int expected_used) {
isc_result_t result;
result = isc_quota_acquire(source);
assert_int_equal(result, expected_result);
switch (expected_result) {
case ISC_R_SUCCESS:
case ISC_R_SOFTQUOTA:
break;
default:
break;
}
assert_int_equal(isc_quota_getused(source), expected_used);
}
ISC_RUN_TEST_IMPL(isc_quota_hard) {
int i;
UNUSED(state);
isc_quota_init(&quota, 100);
for (i = 0; i < 100; i++) {
add_quota(&quota, ISC_R_SUCCESS, i + 1);
}
add_quota(&quota, ISC_R_QUOTA, 100);
assert_int_equal(isc_quota_getused(&quota), 100);
isc_quota_release(&quota);
add_quota(&quota, ISC_R_SUCCESS, 100);
add_quota(&quota, ISC_R_QUOTA, 100);
for (i = 100; i > 0; i--) {
isc_quota_release(&quota);
assert_int_equal(isc_quota_getused(&quota), i - 1);
}
assert_int_equal(isc_quota_getused(&quota), 0);
isc_quota_destroy(&quota);
}
ISC_RUN_TEST_IMPL(isc_quota_soft) {
int i;
UNUSED(state);
isc_quota_init(&quota, 100);
isc_quota_soft(&quota, 50);
for (i = 0; i < 50; i++) {
add_quota(&quota, ISC_R_SUCCESS, i + 1);
}
for (i = 50; i < 100; i++) {
add_quota(&quota, ISC_R_SOFTQUOTA, i + 1);
}
add_quota(&quota, ISC_R_QUOTA, 100);
for (i = 99; i >= 0; i--) {
isc_quota_release(&quota);
assert_int_equal(isc_quota_getused(&quota), i);
}
assert_int_equal(isc_quota_getused(&quota), 0);
isc_quota_destroy(&quota);
}
static atomic_uint_fast32_t cb_calls = 0;
static isc_job_t cbs[30];
static void
callback(void *data) {
int val = *(int *)data;
/* Callback is not called if we get the quota directly */
assert_int_not_equal(val, -1);
/* Verify that the callbacks are called in order */
int v = atomic_fetch_add_relaxed(&cb_calls, 1);
assert_int_equal(v, val);
/*
* First 5 will be detached by the test function,
* for the last 5 - do a 'chain detach'.
*/
if (v >= 5) {
isc_quota_release(&quota);
}
}
ISC_RUN_TEST_IMPL(isc_quota_callback) {
isc_result_t result;
/*
* - 10 calls that end with SUCCESS
* - 10 calls that end with SOFTQUOTA
* - 10 callbacks
*/
int ints[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int i;
UNUSED(state);
isc_quota_init(&quota, 20);
isc_quota_soft(&quota, 10);
for (i = 0; i < 10; i++) {
cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
&ints[i]);
assert_int_equal(result, ISC_R_SUCCESS);
assert_int_equal(isc_quota_getused(&quota), i + 1);
}
for (i = 10; i < 20; i++) {
cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
&ints[i]);
assert_int_equal(result, ISC_R_SOFTQUOTA);
assert_int_equal(isc_quota_getused(&quota), i + 1);
}
for (i = 20; i < 30; i++) {
cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
&ints[i]);
assert_int_equal(result, ISC_R_QUOTA);
assert_int_equal(isc_quota_getused(&quota), 20);
}
assert_int_equal(atomic_load(&cb_calls), 0);
for (i = 0; i < 5; i++) {
isc_quota_release(&quota);
assert_int_equal(isc_quota_getused(&quota), 20);
assert_int_equal(atomic_load(&cb_calls), i + 1);
}
/* That should cause a chain reaction */
isc_quota_release(&quota);
assert_int_equal(atomic_load(&cb_calls), 10);
/* Release the quotas that we did not released in the callback */
for (i = 0; i < 5; i++) {
isc_quota_release(&quota);
}
for (i = 6; i < 20; i++) {
isc_quota_release(&quota);
assert_int_equal(isc_quota_getused(&quota), 19 - i);
}
assert_int_equal(atomic_load(&cb_calls), 10);
assert_int_equal(isc_quota_getused(&quota), 0);
isc_quota_destroy(&quota);
}
/*
* Multithreaded quota callback test:
* - quota set to 100
* - 10 threads, each trying to get 100 quotas.
* - creates a separate thread to release it after 10ms
*/
typedef struct qthreadinfo {
atomic_uint_fast32_t direct;
atomic_uint_fast32_t callback;
isc_job_t callbacks[100];
} qthreadinfo_t;
static atomic_uint_fast32_t g_tnum = 0;
/* at most 10 * 100 quota_detach threads */
isc_thread_t g_threads[10 * 100];
static void *
quota_release(void *arg) {
uv_sleep(10);
isc_quota_release((isc_quota_t *)arg);
return NULL;
}
static void
quota_callback(void *data) {
qthreadinfo_t *qti = (qthreadinfo_t *)data;
atomic_fetch_add_relaxed(&qti->callback, 1);
int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
isc_thread_create(quota_release, &quota, &g_threads[tnum]);
}
static void *
quota_thread(void *qtip) {
qthreadinfo_t *qti = (qthreadinfo_t *)qtip;
for (int i = 0; i < 100; i++) {
qti->callbacks[i] = (isc_job_t)ISC_JOB_INITIALIZER;
isc_result_t result = isc_quota_acquire_cb(
&quota, &qti->callbacks[i], quota_callback, qti);
if (result == ISC_R_SUCCESS) {
atomic_fetch_add_relaxed(&qti->direct, 1);
int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
isc_thread_create(quota_release, &quota,
&g_threads[tnum]);
}
}
return NULL;
}
ISC_RUN_TEST_IMPL(isc_quota_callback_mt) {
UNUSED(state);
int i;
isc_quota_init(&quota, 100);
static qthreadinfo_t qtis[10];
isc_thread_t threads[10];
for (i = 0; i < 10; i++) {
atomic_init(&qtis[i].direct, 0);
atomic_init(&qtis[i].callback, 0);
isc_thread_create(quota_thread, &qtis[i], &threads[i]);
}
for (i = 0; i < 10; i++) {
isc_thread_join(threads[i], NULL);
}
for (i = 0; i < (int)atomic_load(&g_tnum); i++) {
isc_thread_join(g_threads[i], NULL);
}
int direct = 0, ncallback = 0;
for (i = 0; i < 10; i++) {
direct += atomic_load(&qtis[i].direct);
ncallback += atomic_load(&qtis[i].callback);
}
/* Total quota gained must be 10 threads * 100 tries */
assert_int_equal(direct + ncallback, 10 * 100);
/*
* At least 100 must be direct, the rest is virtually random:
* - in a regular run I'm constantly getting 100:900 ratio
* - under rr - usually around ~120:880
* - under rr -h - 1000:0
*/
assert_true(direct >= 100);
isc_quota_destroy(&quota);
}
ISC_TEST_LIST_START
ISC_TEST_ENTRY(isc_quota_get_set)
ISC_TEST_ENTRY(isc_quota_hard)
ISC_TEST_ENTRY(isc_quota_soft)
ISC_TEST_ENTRY(isc_quota_callback)
ISC_TEST_ENTRY(isc_quota_callback_mt)
ISC_TEST_LIST_END
ISC_TEST_MAIN