xref: /netbsd-src/external/mpl/bind/dist/tests/isc/quota_test.c (revision bcda20f65a8566e103791ec395f7f499ef322704)
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(&quota, 100);
43 
44 	assert_int_equal(isc_quota_getmax(&quota), 100);
45 	assert_int_equal(isc_quota_getsoft(&quota), 0);
46 
47 	isc_quota_max(&quota, 50);
48 	isc_quota_soft(&quota, 30);
49 
50 	assert_int_equal(isc_quota_getmax(&quota), 50);
51 	assert_int_equal(isc_quota_getsoft(&quota), 30);
52 
53 	assert_int_equal(isc_quota_getused(&quota), 0);
54 	isc_quota_acquire(&quota);
55 	assert_int_equal(isc_quota_getused(&quota), 1);
56 	isc_quota_release(&quota);
57 	assert_int_equal(isc_quota_getused(&quota), 0);
58 
59 	/* Unlimited */
60 	isc_quota_max(&quota, 0);
61 	isc_quota_soft(&quota, 0);
62 
63 	isc_quota_destroy(&quota);
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(&quota, 100);
90 
91 	for (i = 0; i < 100; i++) {
92 		add_quota(&quota, ISC_R_SUCCESS, i + 1);
93 	}
94 
95 	add_quota(&quota, ISC_R_QUOTA, 100);
96 
97 	assert_int_equal(isc_quota_getused(&quota), 100);
98 
99 	isc_quota_release(&quota);
100 
101 	add_quota(&quota, ISC_R_SUCCESS, 100);
102 	add_quota(&quota, ISC_R_QUOTA, 100);
103 
104 	for (i = 100; i > 0; i--) {
105 		isc_quota_release(&quota);
106 		assert_int_equal(isc_quota_getused(&quota), i - 1);
107 	}
108 	assert_int_equal(isc_quota_getused(&quota), 0);
109 	isc_quota_destroy(&quota);
110 }
111 
112 ISC_RUN_TEST_IMPL(isc_quota_soft) {
113 	int i;
114 	UNUSED(state);
115 
116 	isc_quota_init(&quota, 100);
117 	isc_quota_soft(&quota, 50);
118 
119 	for (i = 0; i < 50; i++) {
120 		add_quota(&quota, ISC_R_SUCCESS, i + 1);
121 	}
122 	for (i = 50; i < 100; i++) {
123 		add_quota(&quota, ISC_R_SOFTQUOTA, i + 1);
124 	}
125 
126 	add_quota(&quota, ISC_R_QUOTA, 100);
127 
128 	for (i = 99; i >= 0; i--) {
129 		isc_quota_release(&quota);
130 		assert_int_equal(isc_quota_getused(&quota), i);
131 	}
132 	assert_int_equal(isc_quota_getused(&quota), 0);
133 	isc_quota_destroy(&quota);
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(&quota);
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(&quota, 20);
172 	isc_quota_soft(&quota, 10);
173 
174 	for (i = 0; i < 10; i++) {
175 		cbs[i] = (isc_job_t)ISC_JOB_INITIALIZER;
176 		result = isc_quota_acquire_cb(&quota, &cbs[i], callback,
177 					      &ints[i]);
178 		assert_int_equal(result, ISC_R_SUCCESS);
179 		assert_int_equal(isc_quota_getused(&quota), 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(&quota, &cbs[i], callback,
184 					      &ints[i]);
185 		assert_int_equal(result, ISC_R_SOFTQUOTA);
186 		assert_int_equal(isc_quota_getused(&quota), 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(&quota, &cbs[i], callback,
192 					      &ints[i]);
193 		assert_int_equal(result, ISC_R_QUOTA);
194 		assert_int_equal(isc_quota_getused(&quota), 20);
195 	}
196 	assert_int_equal(atomic_load(&cb_calls), 0);
197 
198 	for (i = 0; i < 5; i++) {
199 		isc_quota_release(&quota);
200 		assert_int_equal(isc_quota_getused(&quota), 20);
201 		assert_int_equal(atomic_load(&cb_calls), i + 1);
202 	}
203 	/* That should cause a chain reaction */
204 	isc_quota_release(&quota);
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(&quota);
210 	}
211 
212 	for (i = 6; i < 20; i++) {
213 		isc_quota_release(&quota);
214 		assert_int_equal(isc_quota_getused(&quota), 19 - i);
215 	}
216 	assert_int_equal(atomic_load(&cb_calls), 10);
217 
218 	assert_int_equal(isc_quota_getused(&quota), 0);
219 	isc_quota_destroy(&quota);
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, &quota, &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 			&quota, &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, &quota,
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(&quota, 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(&quota);
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