Home | History | Annotate | Line # | Download | only in isc
      1  1.1  christos /*	$NetBSD: quota_test.c,v 1.3 2025/01/26 16:25:50 christos Exp $	*/
      2  1.1  christos 
      3  1.1  christos /*
      4  1.1  christos  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
      5  1.1  christos  *
      6  1.1  christos  * SPDX-License-Identifier: MPL-2.0
      7  1.1  christos  *
      8  1.1  christos  * This Source Code Form is subject to the terms of the Mozilla Public
      9  1.1  christos  * License, v. 2.0. If a copy of the MPL was not distributed with this
     10  1.1  christos  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
     11  1.1  christos  *
     12  1.1  christos  * See the COPYRIGHT file distributed with this work for additional
     13  1.1  christos  * information regarding copyright ownership.
     14  1.1  christos  */
     15  1.1  christos 
     16  1.1  christos #include <inttypes.h>
     17  1.1  christos #include <sched.h> /* IWYU pragma: keep */
     18  1.1  christos #include <setjmp.h>
     19  1.1  christos #include <stdarg.h>
     20  1.1  christos #include <stddef.h>
     21  1.1  christos #include <stdio.h>
     22  1.1  christos #include <stdlib.h>
     23  1.1  christos #include <string.h>
     24  1.1  christos #include <unistd.h>
     25  1.1  christos 
     26  1.1  christos #define UNIT_TESTING
     27  1.1  christos #include <cmocka.h>
     28  1.1  christos 
     29  1.3  christos #include <isc/job.h>
     30  1.1  christos #include <isc/quota.h>
     31  1.1  christos #include <isc/result.h>
     32  1.1  christos #include <isc/thread.h>
     33  1.1  christos #include <isc/util.h>
     34  1.3  christos #include <isc/uv.h>
     35  1.1  christos 
     36  1.3  christos #include <tests/isc.h>
     37  1.1  christos 
     38  1.3  christos isc_quota_t quota;
     39  1.1  christos 
     40  1.1  christos ISC_RUN_TEST_IMPL(isc_quota_get_set) {
     41  1.1  christos 	UNUSED(state);
     42  1.1  christos 	isc_quota_init(&quota, 100);
     43  1.1  christos 
     44  1.1  christos 	assert_int_equal(isc_quota_getmax(&quota), 100);
     45  1.1  christos 	assert_int_equal(isc_quota_getsoft(&quota), 0);
     46  1.1  christos 
     47  1.1  christos 	isc_quota_max(&quota, 50);
     48  1.1  christos 	isc_quota_soft(&quota, 30);
     49  1.1  christos 
     50  1.1  christos 	assert_int_equal(isc_quota_getmax(&quota), 50);
     51  1.1  christos 	assert_int_equal(isc_quota_getsoft(&quota), 30);
     52  1.1  christos 
     53  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 0);
     54  1.3  christos 	isc_quota_acquire(&quota);
     55  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 1);
     56  1.3  christos 	isc_quota_release(&quota);
     57  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 0);
     58  1.3  christos 
     59  1.3  christos 	/* Unlimited */
     60  1.3  christos 	isc_quota_max(&quota, 0);
     61  1.3  christos 	isc_quota_soft(&quota, 0);
     62  1.3  christos 
     63  1.1  christos 	isc_quota_destroy(&quota);
     64  1.1  christos }
     65  1.1  christos 
     66  1.1  christos static void
     67  1.3  christos add_quota(isc_quota_t *source, isc_result_t expected_result,
     68  1.3  christos 	  int expected_used) {
     69  1.1  christos 	isc_result_t result;
     70  1.1  christos 
     71  1.3  christos 	result = isc_quota_acquire(source);
     72  1.1  christos 	assert_int_equal(result, expected_result);
     73  1.1  christos 
     74  1.1  christos 	switch (expected_result) {
     75  1.1  christos 	case ISC_R_SUCCESS:
     76  1.1  christos 	case ISC_R_SOFTQUOTA:
     77  1.1  christos 		break;
     78  1.1  christos 	default:
     79  1.1  christos 		break;
     80  1.1  christos 	}
     81  1.1  christos 
     82  1.1  christos 	assert_int_equal(isc_quota_getused(source), expected_used);
     83  1.1  christos }
     84  1.1  christos 
     85  1.1  christos ISC_RUN_TEST_IMPL(isc_quota_hard) {
     86  1.1  christos 	int i;
     87  1.1  christos 	UNUSED(state);
     88  1.1  christos 
     89  1.1  christos 	isc_quota_init(&quota, 100);
     90  1.1  christos 
     91  1.1  christos 	for (i = 0; i < 100; i++) {
     92  1.3  christos 		add_quota(&quota, ISC_R_SUCCESS, i + 1);
     93  1.1  christos 	}
     94  1.1  christos 
     95  1.3  christos 	add_quota(&quota, ISC_R_QUOTA, 100);
     96  1.1  christos 
     97  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 100);
     98  1.1  christos 
     99  1.3  christos 	isc_quota_release(&quota);
    100  1.1  christos 
    101  1.3  christos 	add_quota(&quota, ISC_R_SUCCESS, 100);
    102  1.3  christos 	add_quota(&quota, ISC_R_QUOTA, 100);
    103  1.1  christos 
    104  1.1  christos 	for (i = 100; i > 0; i--) {
    105  1.3  christos 		isc_quota_release(&quota);
    106  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), i - 1);
    107  1.1  christos 	}
    108  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 0);
    109  1.1  christos 	isc_quota_destroy(&quota);
    110  1.1  christos }
    111  1.1  christos 
    112  1.1  christos ISC_RUN_TEST_IMPL(isc_quota_soft) {
    113  1.1  christos 	int i;
    114  1.1  christos 	UNUSED(state);
    115  1.1  christos 
    116  1.1  christos 	isc_quota_init(&quota, 100);
    117  1.1  christos 	isc_quota_soft(&quota, 50);
    118  1.1  christos 
    119  1.1  christos 	for (i = 0; i < 50; i++) {
    120  1.3  christos 		add_quota(&quota, ISC_R_SUCCESS, i + 1);
    121  1.1  christos 	}
    122  1.1  christos 	for (i = 50; i < 100; i++) {
    123  1.3  christos 		add_quota(&quota, ISC_R_SOFTQUOTA, i + 1);
    124  1.1  christos 	}
    125  1.1  christos 
    126  1.3  christos 	add_quota(&quota, ISC_R_QUOTA, 100);
    127  1.1  christos 
    128  1.1  christos 	for (i = 99; i >= 0; i--) {
    129  1.3  christos 		isc_quota_release(&quota);
    130  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), i);
    131  1.1  christos 	}
    132  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 0);
    133  1.1  christos 	isc_quota_destroy(&quota);
    134  1.1  christos }
    135  1.1  christos 
    136  1.1  christos static atomic_uint_fast32_t cb_calls = 0;
    137  1.3  christos static isc_job_t cbs[30];
    138  1.1  christos 
    139  1.1  christos static void
    140  1.3  christos callback(void *data) {
    141  1.1  christos 	int val = *(int *)data;
    142  1.1  christos 	/* Callback is not called if we get the quota directly */
    143  1.1  christos 	assert_int_not_equal(val, -1);
    144  1.1  christos 
    145  1.1  christos 	/* Verify that the callbacks are called in order */
    146  1.1  christos 	int v = atomic_fetch_add_relaxed(&cb_calls, 1);
    147  1.1  christos 	assert_int_equal(v, val);
    148  1.1  christos 
    149  1.1  christos 	/*
    150  1.1  christos 	 * First 5 will be detached by the test function,
    151  1.1  christos 	 * for the last 5 - do a 'chain detach'.
    152  1.1  christos 	 */
    153  1.1  christos 	if (v >= 5) {
    154  1.3  christos 		isc_quota_release(&quota);
    155  1.1  christos 	}
    156  1.1  christos }
    157  1.1  christos 
    158  1.1  christos ISC_RUN_TEST_IMPL(isc_quota_callback) {
    159  1.1  christos 	isc_result_t result;
    160  1.1  christos 	/*
    161  1.1  christos 	 * - 10 calls that end with SUCCESS
    162  1.1  christos 	 * - 10 calls that end with SOFTQUOTA
    163  1.1  christos 	 * - 10 callbacks
    164  1.1  christos 	 */
    165  1.1  christos 	int ints[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    166  1.1  christos 		       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    167  1.1  christos 		       0,  1,  2,  3,  4,  5,  6,  7,  8,  9 };
    168  1.1  christos 	int i;
    169  1.1  christos 	UNUSED(state);
    170  1.1  christos 
    171  1.1  christos 	isc_quota_init(&quota, 20);
    172  1.1  christos 	isc_quota_soft(&quota, 10);
    173  1.1  christos 
    174  1.1  christos 	for (i = 0; i < 10; i++) {
    175  1.3  christos 		cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
    176  1.3  christos 		result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
    177  1.3  christos 					      &ints[i]);
    178  1.1  christos 		assert_int_equal(result, ISC_R_SUCCESS);
    179  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), i + 1);
    180  1.1  christos 	}
    181  1.1  christos 	for (i = 10; i < 20; i++) {
    182  1.3  christos 		cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
    183  1.3  christos 		result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
    184  1.3  christos 					      &ints[i]);
    185  1.1  christos 		assert_int_equal(result, ISC_R_SOFTQUOTA);
    186  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), i + 1);
    187  1.1  christos 	}
    188  1.1  christos 
    189  1.1  christos 	for (i = 20; i < 30; i++) {
    190  1.3  christos 		cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
    191  1.3  christos 		result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
    192  1.3  christos 					      &ints[i]);
    193  1.1  christos 		assert_int_equal(result, ISC_R_QUOTA);
    194  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), 20);
    195  1.1  christos 	}
    196  1.1  christos 	assert_int_equal(atomic_load(&cb_calls), 0);
    197  1.1  christos 
    198  1.1  christos 	for (i = 0; i < 5; i++) {
    199  1.3  christos 		isc_quota_release(&quota);
    200  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), 20);
    201  1.1  christos 		assert_int_equal(atomic_load(&cb_calls), i + 1);
    202  1.1  christos 	}
    203  1.1  christos 	/* That should cause a chain reaction */
    204  1.3  christos 	isc_quota_release(&quota);
    205  1.1  christos 	assert_int_equal(atomic_load(&cb_calls), 10);
    206  1.1  christos 
    207  1.1  christos 	/* Release the quotas that we did not released in the callback */
    208  1.1  christos 	for (i = 0; i < 5; i++) {
    209  1.3  christos 		isc_quota_release(&quota);
    210  1.1  christos 	}
    211  1.1  christos 
    212  1.1  christos 	for (i = 6; i < 20; i++) {
    213  1.3  christos 		isc_quota_release(&quota);
    214  1.1  christos 		assert_int_equal(isc_quota_getused(&quota), 19 - i);
    215  1.1  christos 	}
    216  1.1  christos 	assert_int_equal(atomic_load(&cb_calls), 10);
    217  1.1  christos 
    218  1.1  christos 	assert_int_equal(isc_quota_getused(&quota), 0);
    219  1.1  christos 	isc_quota_destroy(&quota);
    220  1.1  christos }
    221  1.1  christos 
    222  1.1  christos /*
    223  1.1  christos  * Multithreaded quota callback test:
    224  1.1  christos  * - quota set to 100
    225  1.1  christos  * - 10 threads, each trying to get 100 quotas.
    226  1.1  christos  * - creates a separate thread to release it after 10ms
    227  1.1  christos  */
    228  1.1  christos 
    229  1.1  christos typedef struct qthreadinfo {
    230  1.1  christos 	atomic_uint_fast32_t direct;
    231  1.1  christos 	atomic_uint_fast32_t callback;
    232  1.3  christos 	isc_job_t callbacks[100];
    233  1.1  christos } qthreadinfo_t;
    234  1.1  christos 
    235  1.1  christos static atomic_uint_fast32_t g_tnum = 0;
    236  1.1  christos /* at most 10 * 100 quota_detach threads */
    237  1.1  christos isc_thread_t g_threads[10 * 100];
    238  1.1  christos 
    239  1.1  christos static void *
    240  1.3  christos quota_release(void *arg) {
    241  1.1  christos 	uv_sleep(10);
    242  1.3  christos 	isc_quota_release((isc_quota_t *)arg);
    243  1.3  christos 	return NULL;
    244  1.1  christos }
    245  1.1  christos 
    246  1.1  christos static void
    247  1.3  christos quota_callback(void *data) {
    248  1.1  christos 	qthreadinfo_t *qti = (qthreadinfo_t *)data;
    249  1.1  christos 	atomic_fetch_add_relaxed(&qti->callback, 1);
    250  1.1  christos 	int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
    251  1.3  christos 	isc_thread_create(quota_release, &quota, &g_threads[tnum]);
    252  1.1  christos }
    253  1.1  christos 
    254  1.3  christos static void *
    255  1.1  christos quota_thread(void *qtip) {
    256  1.1  christos 	qthreadinfo_t *qti = (qthreadinfo_t *)qtip;
    257  1.1  christos 	for (int i = 0; i < 100; i++) {
    258  1.3  christos 		qti->callbacks[i] = (isc_job_t)ISC_JOB_INITIALIZER;
    259  1.3  christos 		isc_result_t result = isc_quota_acquire_cb(
    260  1.3  christos 			&quota, &qti->callbacks[i], quota_callback, qti);
    261  1.1  christos 		if (result == ISC_R_SUCCESS) {
    262  1.1  christos 			atomic_fetch_add_relaxed(&qti->direct, 1);
    263  1.1  christos 			int tnum = atomic_fetch_add_relaxed(&g_tnum, 1);
    264  1.3  christos 			isc_thread_create(quota_release, &quota,
    265  1.1  christos 					  &g_threads[tnum]);
    266  1.1  christos 		}
    267  1.1  christos 	}
    268  1.3  christos 	return NULL;
    269  1.1  christos }
    270  1.1  christos 
    271  1.1  christos ISC_RUN_TEST_IMPL(isc_quota_callback_mt) {
    272  1.1  christos 	UNUSED(state);
    273  1.1  christos 	int i;
    274  1.1  christos 
    275  1.1  christos 	isc_quota_init(&quota, 100);
    276  1.1  christos 	static qthreadinfo_t qtis[10];
    277  1.1  christos 	isc_thread_t threads[10];
    278  1.1  christos 	for (i = 0; i < 10; i++) {
    279  1.1  christos 		atomic_init(&qtis[i].direct, 0);
    280  1.1  christos 		atomic_init(&qtis[i].callback, 0);
    281  1.1  christos 		isc_thread_create(quota_thread, &qtis[i], &threads[i]);
    282  1.1  christos 	}
    283  1.1  christos 	for (i = 0; i < 10; i++) {
    284  1.1  christos 		isc_thread_join(threads[i], NULL);
    285  1.1  christos 	}
    286  1.1  christos 
    287  1.1  christos 	for (i = 0; i < (int)atomic_load(&g_tnum); i++) {
    288  1.1  christos 		isc_thread_join(g_threads[i], NULL);
    289  1.1  christos 	}
    290  1.1  christos 	int direct = 0, ncallback = 0;
    291  1.1  christos 
    292  1.1  christos 	for (i = 0; i < 10; i++) {
    293  1.1  christos 		direct += atomic_load(&qtis[i].direct);
    294  1.1  christos 		ncallback += atomic_load(&qtis[i].callback);
    295  1.1  christos 	}
    296  1.1  christos 	/* Total quota gained must be 10 threads * 100 tries */
    297  1.1  christos 	assert_int_equal(direct + ncallback, 10 * 100);
    298  1.1  christos 	/*
    299  1.1  christos 	 * At least 100 must be direct, the rest is virtually random:
    300  1.1  christos 	 * - in a regular run I'm constantly getting 100:900 ratio
    301  1.1  christos 	 * - under rr - usually around ~120:880
    302  1.1  christos 	 * - under rr -h - 1000:0
    303  1.1  christos 	 */
    304  1.1  christos 	assert_true(direct >= 100);
    305  1.1  christos 
    306  1.1  christos 	isc_quota_destroy(&quota);
    307  1.1  christos }
    308  1.1  christos 
    309  1.1  christos ISC_TEST_LIST_START
    310  1.1  christos 
    311  1.1  christos ISC_TEST_ENTRY(isc_quota_get_set)
    312  1.1  christos ISC_TEST_ENTRY(isc_quota_hard)
    313  1.1  christos ISC_TEST_ENTRY(isc_quota_soft)
    314  1.1  christos ISC_TEST_ENTRY(isc_quota_callback)
    315  1.1  christos ISC_TEST_ENTRY(isc_quota_callback_mt)
    316  1.1  christos 
    317  1.1  christos ISC_TEST_LIST_END
    318  1.1  christos 
    319  1.1  christos ISC_TEST_MAIN
    320