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("a, 100); 43 1.1 christos 44 1.1 christos assert_int_equal(isc_quota_getmax("a), 100); 45 1.1 christos assert_int_equal(isc_quota_getsoft("a), 0); 46 1.1 christos 47 1.1 christos isc_quota_max("a, 50); 48 1.1 christos isc_quota_soft("a, 30); 49 1.1 christos 50 1.1 christos assert_int_equal(isc_quota_getmax("a), 50); 51 1.1 christos assert_int_equal(isc_quota_getsoft("a), 30); 52 1.1 christos 53 1.1 christos assert_int_equal(isc_quota_getused("a), 0); 54 1.3 christos isc_quota_acquire("a); 55 1.1 christos assert_int_equal(isc_quota_getused("a), 1); 56 1.3 christos isc_quota_release("a); 57 1.1 christos assert_int_equal(isc_quota_getused("a), 0); 58 1.3 christos 59 1.3 christos /* Unlimited */ 60 1.3 christos isc_quota_max("a, 0); 61 1.3 christos isc_quota_soft("a, 0); 62 1.3 christos 63 1.1 christos isc_quota_destroy("a); 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("a, 100); 90 1.1 christos 91 1.1 christos for (i = 0; i < 100; i++) { 92 1.3 christos add_quota("a, ISC_R_SUCCESS, i + 1); 93 1.1 christos } 94 1.1 christos 95 1.3 christos add_quota("a, ISC_R_QUOTA, 100); 96 1.1 christos 97 1.1 christos assert_int_equal(isc_quota_getused("a), 100); 98 1.1 christos 99 1.3 christos isc_quota_release("a); 100 1.1 christos 101 1.3 christos add_quota("a, ISC_R_SUCCESS, 100); 102 1.3 christos add_quota("a, 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("a); 106 1.1 christos assert_int_equal(isc_quota_getused("a), i - 1); 107 1.1 christos } 108 1.1 christos assert_int_equal(isc_quota_getused("a), 0); 109 1.1 christos isc_quota_destroy("a); 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("a, 100); 117 1.1 christos isc_quota_soft("a, 50); 118 1.1 christos 119 1.1 christos for (i = 0; i < 50; i++) { 120 1.3 christos add_quota("a, 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("a, ISC_R_SOFTQUOTA, i + 1); 124 1.1 christos } 125 1.1 christos 126 1.3 christos add_quota("a, 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("a); 130 1.1 christos assert_int_equal(isc_quota_getused("a), i); 131 1.1 christos } 132 1.1 christos assert_int_equal(isc_quota_getused("a), 0); 133 1.1 christos isc_quota_destroy("a); 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("a); 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("a, 20); 172 1.1 christos isc_quota_soft("a, 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("a, &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("a), 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("a, &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("a), 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("a, &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("a), 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("a); 200 1.1 christos assert_int_equal(isc_quota_getused("a), 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("a); 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("a); 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("a); 214 1.1 christos assert_int_equal(isc_quota_getused("a), 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("a), 0); 219 1.1 christos isc_quota_destroy("a); 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, "a, &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 "a, &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, "a, 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("a, 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("a); 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