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