xref: /xnu-11215.81.4/tests/os_refcnt.c (revision d4514f0bc1d3f944c22d92e68b646ac3fb40d452)
1 #include <darwintest.h>
2 #include <darwintest_utils.h>
3 #include <stdio.h>
4 #include <assert.h>
5 #include <setjmp.h>
6 #include <os/tsd.h>
7 
8 #define DEVELOPMENT 1
9 #define DEBUG 0
10 #define XNU_KERNEL_PRIVATE 1
11 
12 #define OS_REFCNT_DEBUG 1
13 #define STRESS_TESTS 0
14 #define __zpercpu
15 
16 #pragma clang diagnostic ignored "-Watomic-implicit-seq-cst"
17 #pragma clang diagnostic ignored "-Wc++98-compat"
18 
19 __abortlike
20 void handle_panic(const char *func, char *str, ...);
21 #define panic(...) handle_panic(__func__, __VA_ARGS__)
22 
23 #define ZPERCPU_STRIDE 128
24 
25 static inline int
zpercpu_count(void)26 zpercpu_count(void)
27 {
28 	static int n;
29 	if (__improbable(n == 0)) {
30 		n = dt_ncpu();
31 	}
32 	return n;
33 }
34 
35 static inline void
thread_wakeup(void * event)36 thread_wakeup(void *event)
37 {
38 	abort();
39 }
40 
41 #define zalloc_percpu(zone, flags) \
42 	(uint64_t _Atomic *)calloc((size_t)zpercpu_count(), ZPERCPU_STRIDE)
43 
44 #define zfree_percpu(zone, ptr) \
45 	free(ptr)
46 
47 static inline uint64_t _Atomic *
zpercpu_get_cpu(uint64_t _Atomic * ptr,int cpu)48 zpercpu_get_cpu(uint64_t _Atomic *ptr, int cpu)
49 {
50 	return (uint64_t _Atomic *)((uintptr_t)ptr + (uintptr_t)cpu * ZPERCPU_STRIDE);
51 }
52 
53 #define zpercpu_get(ptr)  zpercpu_get_cpu(ptr, 0)
54 
55 #define zpercpu_foreach_cpu(cpu) \
56 	for (int cpu = 0, __n = zpercpu_count(); cpu < __n; cpu++)
57 
58 #define zpercpu_foreach(cpu) \
59 	for (int cpu = 0, __n = zpercpu_count(); cpu < __n; cpu++)
60 
61 #define cpu_number() (int)_os_cpu_number()
62 
63 #include "../libkern/os/refcnt.h"
64 #include "../libkern/os/refcnt.c"
65 
66 T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true));
67 
68 /* import some of the refcnt internal state for testing */
69 extern bool ref_debug_enable;
70 os_refgrp_decl_extern(global_ref_group);
71 
72 T_GLOBAL_META(
73 	T_META_NAMESPACE("os_refcnt"),
74 	T_META_CHECK_LEAKS(false)
75 	);
76 
77 T_DECL(os_refcnt, "Basic atomic refcount")
78 {
79 	struct os_refcnt rc;
80 	os_ref_init(&rc, NULL);
81 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 1, "refcount correctly initialized");
82 
83 	os_ref_retain(&rc);
84 	os_ref_retain(&rc);
85 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 3, "retain increased count");
86 
87 	os_ref_count_t x = os_ref_release(&rc);
88 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 2, "release decreased count");
89 	T_ASSERT_EQ_UINT(x, 2, "release returned correct count");
90 
91 	os_ref_release_live(&rc);
92 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 1, "release_live decreased count");
93 
94 	x = os_ref_release(&rc);
95 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 0, "released");
96 	T_ASSERT_EQ_UINT(x, 0, "returned released");
97 
98 	os_ref_init(&rc, NULL);
99 	T_ASSERT_TRUE(os_ref_retain_try(&rc), "try retained");
100 
101 	(void)os_ref_release(&rc);
102 	(void)os_ref_release(&rc);
103 	T_QUIET; T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 0, "release");
104 
105 	T_ASSERT_FALSE(os_ref_retain_try(&rc), "try failed");
106 }
107 
108 T_DECL(os_pcpu_refcnt, "Basic atomic refcount")
109 {
110 	dispatch_queue_t rq = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
111 	dispatch_group_t g = dispatch_group_create();
112 	os_pcpu_ref_t rc;
113 
114 	os_pcpu_ref_init(&rc, NULL);
115 	T_ASSERT_EQ_UINT(os_pcpu_ref_count(rc), OS_REFCNT_MAX_COUNT,
116 	    "refcount correctly initialized");
117 
118 	dispatch_group_async(g, rq, ^{
119 		os_pcpu_ref_retain(rc, NULL);
120 	});
121 	dispatch_group_async(g, rq, ^{
122 		T_ASSERT_TRUE(os_pcpu_ref_retain_try(rc, NULL), "try succeeded");
123 	});
124 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
125 
126 	T_ASSERT_EQ_UINT(os_pcpu_ref_count(rc), OS_REFCNT_MAX_COUNT,
127 	    "retain increased count");
128 
129 	T_ASSERT_EQ_UINT(os_pcpu_ref_kill(rc, NULL), 2,
130 	    "kill decreased count");
131 	T_ASSERT_EQ_UINT(os_pcpu_ref_count(rc), 2,
132 	    "kill decreased count");
133 
134 	T_ASSERT_FALSE(os_pcpu_ref_retain_try(rc, NULL), "try failed");
135 
136 	os_pcpu_ref_release_live(rc, NULL);
137 	T_ASSERT_EQ_UINT(os_pcpu_ref_count(rc), 1, "release_live decreased count");
138 
139 	T_ASSERT_EQ_UINT(os_pcpu_ref_release(rc, NULL), 0, "returned released");
140 	T_ASSERT_EQ_UINT(os_pcpu_ref_count(rc), 0, "released");
141 
142 	os_pcpu_ref_destroy(&rc, NULL);
143 }
144 
145 T_DECL(refcnt_raw, "Raw refcount")
146 {
147 	os_ref_atomic_t rc;
148 	os_ref_init_raw(&rc, NULL);
149 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 1, "refcount correctly initialized");
150 
151 	os_ref_retain_raw(&rc, NULL);
152 	os_ref_retain_raw(&rc, NULL);
153 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 3, "retain increased count");
154 
155 	os_ref_count_t x = os_ref_release_raw(&rc, NULL);
156 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 2, "release decreased count");
157 	T_ASSERT_EQ_UINT(x, 2, "release returned correct count");
158 
159 	os_ref_release_live_raw(&rc, NULL);
160 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 1, "release_live decreased count");
161 
162 	x = os_ref_release_raw(&rc, NULL);
163 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 0, "released");
164 	T_ASSERT_EQ_UINT(x, 0, "returned released");
165 
166 	os_ref_init_raw(&rc, NULL);
167 	T_ASSERT_TRUE(os_ref_retain_try_raw(&rc, NULL), "try retained");
168 
169 	(void)os_ref_release_raw(&rc, NULL);
170 	(void)os_ref_release_raw(&rc, NULL);
171 	T_QUIET; T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 0, "release");
172 
173 	T_ASSERT_FALSE(os_ref_retain_try_raw(&rc, NULL), "try failed");
174 }
175 
176 T_DECL(refcnt_locked, "Locked refcount")
177 {
178 	struct os_refcnt rc;
179 	os_ref_init(&rc, NULL);
180 
181 	os_ref_retain_locked(&rc);
182 	os_ref_retain_locked(&rc);
183 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 3, "retain increased count");
184 
185 	os_ref_count_t x = os_ref_release_locked(&rc);
186 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 2, "release decreased count");
187 	T_ASSERT_EQ_UINT(x, 2, "release returned correct count");
188 
189 	(void)os_ref_release_locked(&rc);
190 	x = os_ref_release_locked(&rc);
191 	T_ASSERT_EQ_UINT(os_ref_get_count(&rc), 0, "released");
192 	T_ASSERT_EQ_UINT(x, 0, "returned released");
193 }
194 
195 T_DECL(refcnt_raw_locked, "Locked raw refcount")
196 {
197 	os_ref_atomic_t rc;
198 	os_ref_init_raw(&rc, NULL);
199 
200 	os_ref_retain_locked_raw(&rc, NULL);
201 	os_ref_retain_locked_raw(&rc, NULL);
202 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 3, "retain increased count");
203 
204 	os_ref_count_t x = os_ref_release_locked_raw(&rc, NULL);
205 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 2, "release decreased count");
206 	T_ASSERT_EQ_UINT(x, 2, "release returned correct count");
207 
208 	(void)os_ref_release_locked_raw(&rc, NULL);
209 	x = os_ref_release_locked_raw(&rc, NULL);
210 	T_ASSERT_EQ_UINT(os_ref_get_count_raw(&rc), 0, "released");
211 	T_ASSERT_EQ_UINT(x, 0, "returned released");
212 }
213 
214 static void
do_bitwise_test(const os_ref_count_t bits)215 do_bitwise_test(const os_ref_count_t bits)
216 {
217 	os_ref_atomic_t rc;
218 	os_ref_count_t reserved = 0xaaaaaaaaU & ((1U << bits) - 1);
219 
220 	T_LOG("do_bitwise_test(nbits:%d, reserved:%#x)", bits, reserved);
221 
222 	os_ref_init_count_mask(&rc, bits, NULL, 1, reserved);
223 
224 	T_ASSERT_EQ_UINT(os_ref_get_count_mask(&rc, bits), 1, "[%u bits] refcount initialized", bits);
225 
226 	os_ref_retain_mask(&rc, bits, NULL);
227 	os_ref_retain_mask(&rc, bits, NULL);
228 	T_ASSERT_EQ_UINT(os_ref_get_count_mask(&rc, bits), 3, "retain increased count");
229 
230 	os_ref_count_t x = os_ref_release_mask(&rc, bits, NULL);
231 	T_ASSERT_EQ_UINT(x, 2, "release returned correct count");
232 
233 	os_ref_release_live_mask(&rc, bits, NULL);
234 	T_ASSERT_EQ_UINT(os_ref_get_count_mask(&rc, bits), 1, "release_live decreased count");
235 
236 	x = os_ref_release_mask(&rc, bits, NULL);
237 	T_ASSERT_EQ_UINT(os_ref_get_count_mask(&rc, bits), 0, "released");
238 	T_ASSERT_EQ_UINT(x, 0, "returned released");
239 
240 	T_ASSERT_EQ_UINT(rc & ((1U << bits) - 1), reserved, "Reserved bits not modified");
241 
242 	os_ref_init_count_mask(&rc, bits, NULL, 1, reserved);
243 	T_ASSERT_TRUE(os_ref_retain_try_mask(&rc, bits, 0, NULL), "try retained");
244 	if (reserved) {
245 		T_ASSERT_FALSE(os_ref_retain_try_mask(&rc, bits, reserved, NULL), "try reject");
246 	}
247 
248 	(void)os_ref_release_mask(&rc, bits, NULL);
249 	(void)os_ref_release_mask(&rc, bits, NULL);
250 	T_QUIET; T_ASSERT_EQ_UINT(os_ref_get_count_mask(&rc, bits), 0, "release");
251 
252 	T_ASSERT_FALSE(os_ref_retain_try_mask(&rc, bits, 0, NULL), "try fail");
253 
254 	T_ASSERT_EQ_UINT(os_ref_get_bits_mask(&rc, bits), reserved, "Reserved bits not modified");
255 }
256 
257 T_DECL(refcnt_bitwise, "Bitwise refcount")
258 {
259 	do_bitwise_test(0);
260 	do_bitwise_test(1);
261 	do_bitwise_test(8);
262 	do_bitwise_test(26);
263 
264 	os_ref_atomic_t rc = 0xaaaaaaaa;
265 
266 	const os_ref_count_t nbits = 3;
267 	const os_ref_count_t count = 5;
268 	const os_ref_count_t bits = 7;
269 	os_ref_init_count_mask(&rc, nbits, NULL, count, bits);
270 
271 	os_ref_count_t mask = (1U << nbits) - 1;
272 	T_ASSERT_EQ_UINT(rc & mask, bits, "bits correctly initialized");
273 	T_ASSERT_EQ_UINT(rc >> nbits, count, "count correctly initialized");
274 }
275 
276 os_refgrp_decl(static, g1, "test group", NULL);
277 os_refgrp_decl_extern(g1);
278 
279 T_DECL(refcnt_groups, "Group accounting")
280 {
281 #if OS_REFCNT_DEBUG
282 	ref_debug_enable = true;
283 
284 	struct os_refcnt rc;
285 	os_ref_init(&rc, &g1);
286 
287 	T_ASSERT_EQ_UINT(g1.grp_children, 1, "group attached");
288 	T_ASSERT_EQ_UINT(global_ref_group.grp_children, 1, "global group attached");
289 	T_ASSERT_EQ_UINT(g1.grp_count, 1, "group count");
290 	T_ASSERT_EQ_ULLONG(g1.grp_retain_total, 1ULL, "group retains");
291 	T_ASSERT_EQ_ULLONG(g1.grp_release_total, 0ULL, "group releases");
292 
293 	os_ref_retain(&rc);
294 	os_ref_retain(&rc);
295 	os_ref_release_live(&rc);
296 	os_ref_release_live(&rc);
297 
298 	T_EXPECT_EQ_ULLONG(g1.grp_retain_total, 3ULL, "group retains");
299 	T_EXPECT_EQ_ULLONG(g1.grp_release_total, 2ULL, "group releases");
300 
301 	os_ref_count_t x = os_ref_release(&rc);
302 	T_QUIET; T_ASSERT_EQ_UINT(x, 0, "released");
303 
304 	T_ASSERT_EQ_UINT(g1.grp_children, 0, "group detatched");
305 	T_ASSERT_EQ_UINT(g1.grp_count, 0, "group count");
306 #else
307 	T_SKIP("Refcount debugging disabled");
308 #endif
309 }
310 
311 enum {
312 	OSREF_UNDERFLOW    = 1,
313 	OSREF_OVERFLOW     = 2,
314 	OSREF_RETAIN       = 3,
315 	OSREF_DEALLOC_LIVE = 4,
316 };
317 
318 static jmp_buf jb;
319 static bool expect_panic = false;
320 
321 void
handle_panic(const char * func,char * __unused str,...)322 handle_panic(const char *func, char *__unused str, ...)
323 {
324 	int ret = -1;
325 	if (!expect_panic) {
326 		T_FAIL("unexpected panic from %s", func);
327 		T_LOG("corrupt program state, aborting");
328 		abort();
329 	}
330 	expect_panic = false;
331 
332 	if (strcmp(func, "os_ref_panic_underflow") == 0) {
333 		ret = OSREF_UNDERFLOW;
334 	} else if (strcmp(func, "os_ref_panic_overflow") == 0) {
335 		ret = OSREF_OVERFLOW;
336 	} else if (strcmp(func, "os_ref_panic_retain") == 0) {
337 		ret = OSREF_RETAIN;
338 	} else if (strcmp(func, "os_ref_panic_live") == 0) {
339 		ret = OSREF_DEALLOC_LIVE;
340 	} else {
341 		T_LOG("unexpected panic from %s", func);
342 	}
343 
344 	longjmp(jb, ret);
345 }
346 
347 T_DECL(refcnt_underflow, "Underflow")
348 {
349 	os_ref_atomic_t rc;
350 	os_ref_init_raw(&rc, NULL);
351 	(void)os_ref_release_raw(&rc, NULL);
352 
353 	int x = setjmp(jb);
354 	if (x == 0) {
355 		expect_panic = true;
356 		(void)os_ref_release_raw(&rc, NULL);
357 		T_FAIL("underflow not caught");
358 	} else {
359 		T_ASSERT_EQ_INT(x, OSREF_UNDERFLOW, "underflow caught");
360 	}
361 }
362 
363 T_DECL(refcnt_overflow, "Overflow")
364 {
365 	os_ref_atomic_t rc;
366 	os_ref_init_count_raw(&rc, NULL, 0x0fffffffU);
367 
368 	int x = setjmp(jb);
369 	if (x == 0) {
370 		expect_panic = true;
371 		(void)os_ref_retain_raw(&rc, NULL);
372 		T_FAIL("overflow not caught");
373 	} else {
374 		T_ASSERT_EQ_INT(x, OSREF_RETAIN, "overflow caught");
375 	}
376 }
377 
378 T_DECL(refcnt_resurrection, "Resurrection")
379 {
380 	os_ref_atomic_t rc;
381 	os_ref_init_raw(&rc, NULL);
382 	os_ref_count_t n = os_ref_release_raw(&rc, NULL);
383 
384 	T_QUIET; T_EXPECT_EQ_UINT(n, 0, "reference not released");
385 
386 	int x = setjmp(jb);
387 	if (x == 0) {
388 		expect_panic = true;
389 		(void)os_ref_retain_raw(&rc, NULL);
390 		T_FAIL("resurrection not caught");
391 	} else {
392 		T_ASSERT_EQ_INT(x, OSREF_RETAIN, "resurrection caught");
393 	}
394 }
395 
396 T_DECL(refcnt_dealloc_live, "Dealloc expected live object")
397 {
398 	os_ref_atomic_t rc;
399 	os_ref_init_raw(&rc, NULL);
400 
401 	expect_panic = true;
402 	int x = setjmp(jb);
403 	if (x == 0) {
404 		expect_panic = true;
405 		os_ref_release_live_raw(&rc, NULL);
406 		T_FAIL("dealloc live not caught");
407 	} else {
408 		T_ASSERT_EQ_INT(x, OSREF_DEALLOC_LIVE, "dealloc live caught");
409 	}
410 }
411 
412 T_DECL(refcnt_initializer, "Static intializers")
413 {
414 	struct os_refcnt rc = OS_REF_INITIALIZER;
415 	os_ref_atomic_t rca = OS_REF_ATOMIC_INITIALIZER;
416 
417 	T_ASSERT_EQ_INT(0, os_ref_retain_try(&rc), NULL);
418 	T_ASSERT_EQ_INT(0, os_ref_get_count_raw(&rca), NULL);
419 }
420 
421 #if STRESS_TESTS
422 
423 static unsigned pcpu_perf_step = 0;
424 
425 static void
worker_ref(os_ref_atomic_t * rc,unsigned long * count)426 worker_ref(os_ref_atomic_t *rc, unsigned long *count)
427 {
428 	unsigned long n = 0;
429 
430 	while (os_atomic_load(&pcpu_perf_step, relaxed) == 0) {
431 	}
432 
433 	while (os_atomic_load(&pcpu_perf_step, relaxed) == 1) {
434 		os_ref_retain_raw(rc, NULL);
435 		os_ref_release_live_raw(rc, NULL);
436 		n++;
437 	}
438 
439 	os_atomic_add(count, n, relaxed);
440 }
441 
442 static void
worker_pcpu_ref(os_pcpu_ref_t rc,unsigned long * count)443 worker_pcpu_ref(os_pcpu_ref_t rc, unsigned long *count)
444 {
445 	unsigned long n = 0;
446 
447 	while (os_atomic_load(&pcpu_perf_step, relaxed) == 0) {
448 	}
449 
450 	while (os_atomic_load(&pcpu_perf_step, relaxed) == 1) {
451 		os_pcpu_ref_retain(rc, NULL);
452 		os_pcpu_ref_release_live(rc, NULL);
453 		n++;
454 	}
455 
456 	os_atomic_add(count, n, relaxed);
457 }
458 
459 #define PCPU_BENCH_LEN   2
460 
461 static void
warmup_thread_pool(dispatch_group_t g,dispatch_queue_t rq)462 warmup_thread_pool(dispatch_group_t g, dispatch_queue_t rq)
463 {
464 	os_atomic_store(&pcpu_perf_step, 1, relaxed);
465 
466 	zpercpu_foreach_cpu(cpu) {
467 		dispatch_group_async(g, rq, ^{
468 			while (os_atomic_load(&pcpu_perf_step, relaxed) == 1) {
469 			}
470 		});
471 	}
472 
473 	os_atomic_store(&pcpu_perf_step, 0, relaxed);
474 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
475 }
476 
477 T_DECL(pcpu_perf, "Performance per-cpu")
478 {
479 	os_ref_atomic_t rc;
480 	os_pcpu_ref_t prc;
481 	__block unsigned long count = 0;
482 	double scale = PCPU_BENCH_LEN * 1e6;
483 	dispatch_queue_t rq = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
484 	dispatch_group_t g = dispatch_group_create();
485 
486 	os_ref_init_raw(&rc, NULL);
487 	os_pcpu_ref_init(&prc, NULL);
488 
489 	T_LOG("uncontended benchmark");
490 
491 	dispatch_group_async(g, rq, ^{
492 		worker_ref(&rc, &count);
493 	});
494 
495 	count = 0;
496 	os_atomic_store(&pcpu_perf_step, 1, relaxed);
497 	sleep(PCPU_BENCH_LEN);
498 	os_atomic_store(&pcpu_perf_step, 0, relaxed);
499 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
500 
501 	T_PASS("%.2fM rounds per thread per second (atomic)", count / scale);
502 
503 	dispatch_group_async(g, rq, ^{
504 		worker_pcpu_ref(prc, &count);
505 	});
506 
507 	count = 0;
508 	os_atomic_store(&pcpu_perf_step, 1, relaxed);
509 	sleep(PCPU_BENCH_LEN);
510 	os_atomic_store(&pcpu_perf_step, 0, relaxed);
511 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
512 
513 	T_PASS("%.2fM rounds per thread per second (pcpu)", count / scale);
514 
515 	T_LOG("contended benchmark");
516 
517 	warmup_thread_pool(g, rq);
zpercpu_foreach_cpu(cpu)518 	zpercpu_foreach_cpu(cpu) {
519 		dispatch_group_async(g, rq, ^{
520 			worker_ref(&rc, &count);
521 		});
522 	}
523 
524 	count = 0;
525 	os_atomic_store(&pcpu_perf_step, 1, relaxed);
526 	sleep(PCPU_BENCH_LEN);
527 	os_atomic_store(&pcpu_perf_step, 0, relaxed);
528 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
529 
530 	T_PASS("%.2fM rounds per thread per second (atomic)", count / (zpercpu_count() * scale));
531 
532 	warmup_thread_pool(g, rq);
zpercpu_foreach_cpu(cpu)533 	zpercpu_foreach_cpu(cpu) {
534 		dispatch_group_async(g, rq, ^{
535 			worker_pcpu_ref(prc, &count);
536 		});
537 	}
538 
539 	count = 0;
540 	os_atomic_store(&pcpu_perf_step, 1, relaxed);
541 	sleep(PCPU_BENCH_LEN);
542 	os_atomic_store(&pcpu_perf_step, 0, relaxed);
543 	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
544 
545 	T_PASS("%.2fM rounds per thread per second (pcpu)", count / (zpercpu_count() * scale));
546 
547 	(void)os_pcpu_ref_kill(prc, NULL);
548 	os_pcpu_ref_destroy(&prc, NULL);
549 }
550 
551 static const unsigned long iters = 1024 * 1024 * 32;
552 
553 static void *
func(void * _rc)554 func(void *_rc)
555 {
556 	struct os_refcnt *rc = _rc;
557 	for (unsigned long i = 0; i < iters; i++) {
558 		os_ref_retain(rc);
559 		os_ref_release_live(rc);
560 	}
561 	return NULL;
562 }
563 
564 T_DECL(refcnt_stress, "Stress test")
565 {
566 	pthread_t th1, th2;
567 
568 	struct os_refcnt rc;
569 	os_ref_init(&rc, NULL);
570 
571 	T_ASSERT_POSIX_ZERO(pthread_create(&th1, NULL, func, &rc), "pthread_create");
572 	T_ASSERT_POSIX_ZERO(pthread_create(&th2, NULL, func, &rc), "pthread_create");
573 
574 	void *r1, *r2;
575 	T_ASSERT_POSIX_ZERO(pthread_join(th1, &r1), "pthread_join");
576 	T_ASSERT_POSIX_ZERO(pthread_join(th2, &r2), "pthread_join");
577 
578 	os_ref_count_t x = os_ref_release(&rc);
579 	T_ASSERT_EQ_INT(x, 0, "Consistent refcount");
580 }
581 
582 #endif
583