xref: /xnu-12377.1.9/tests/unit/fibers_test.c (revision f6217f891ac0bb64f3d375211650a4c1ff8ca1ea)
1 /*
2  * Copyright (c) 2000-2025 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 #include <darwintest.h>
30 
31 #include "mocks/std_safe.h"
32 #include "mocks/mock_thread.h"
33 
34 #include "mocks/fibers/fibers.h"
35 #include "mocks/fibers/mutex.h"
36 #include "mocks/fibers/condition.h"
37 #include "mocks/fibers/random.h"
38 
39 // Use FIBERS_PREEMPTION=1 to have simulated preemption at memory operations.
40 // make -C tests/unit SDKROOT=macosx.internal fibers_test FIBERS_PREEMPTION=1
41 
42 #define UT_MODULE osfmk
43 T_GLOBAL_META(
44 	T_META_NAMESPACE("xnu.unit.fibers"),
45 	T_META_RADAR_COMPONENT_NAME("xnu"),
46 	T_META_OWNER("a_fioraldi"),
47 	T_META_RUN_CONCURRENTLY(false)
48 	);
49 // use fibers for scheduling
50 UT_USE_FIBERS(1);
51 // use the data race checker
52 // UT_FIBERS_USE_CHECKER(1);
53 
54 static int third_fiber_id = -1;
55 static void*
coop_fibers_func(void * x)56 coop_fibers_func(void* x)
57 {
58 	int *cooperative_counter = (int*)x;
59 
60 	if (*cooperative_counter == 0) {
61 		// main thread can jump here just after fibers_create
62 		fibers_yield_to(0); // switch back to main thread and finish the fibers creation
63 	}
64 
65 	T_QUIET; T_ASSERT_EQ(*cooperative_counter, fibers_current->id, "invalid cooperative_counter");
66 	*cooperative_counter = fibers_current->id + 1;
67 
68 	// switch to next fiber or to main fiber (id=0) if the current is the last
69 	if (fibers_current->id == third_fiber_id) {
70 		fibers_yield_to(0);
71 	} else {
72 		fibers_yield_to(fibers_current->id + 1);
73 	}
74 
75 	return NULL;
76 }
77 
78 T_DECL(coop_fibers, "cooperative scheduling using fibers")
79 {
80 	// disable preemption in case FIBERS_PREEMPTION=1 was using to compile
81 	// context switches will still happen before and after locks / interrupt enable/disable / fibers creation
82 	fibers_may_yield_probability = 0;
83 
84 	random_set_seed(1234);
85 
86 	int cooperative_counter = 0;
87 
88 	fiber_t first = fibers_create(FIBERS_DEFAULT_STACK_SIZE, coop_fibers_func, (void*)&cooperative_counter);
89 	fiber_t second = fibers_create(FIBERS_DEFAULT_STACK_SIZE, coop_fibers_func, (void*)&cooperative_counter);
90 	fiber_t third = fibers_create(FIBERS_DEFAULT_STACK_SIZE, coop_fibers_func, (void*)&cooperative_counter);
91 
92 	third_fiber_id = third->id;
93 
94 	// Start the chain of ctxswitches from the main thread and switch to first
95 	cooperative_counter = first->id;
96 	fibers_yield_to(first->id);
97 
98 	T_LOG("Done cooperative_counter=%d", cooperative_counter);
99 	T_ASSERT_EQ(cooperative_counter, third->id + 1, "invalid cooperative schedule");
100 
101 	// always join the fibers
102 	fibers_join(first);
103 	fibers_join(second);
104 	fibers_join(third);
105 
106 	T_PASS("coop_fibers");
107 }
108 
109 static int global_var;
110 static void*
tiny_race_func(void * x)111 tiny_race_func(void* x)
112 {
113 	global_var = 42;
114 	return x;
115 }
116 
117 // Standard ThreadSanitizer example in the llvm doc to showcase a race
118 // TSan will not fail the test by default, you beed to set halt_on_error=1 in TSAN_OPTIONS
119 // the test will just run fine without TSan, the data race between fibers can be detected with the fibers data race checker too
120 T_DECL(tsan_tiny_race, "tsan_tiny_race")
121 {
122 	// This sometimes triggers a ThreadSanitizer data race depending on the OS scheduler
123 	pthread_t thread;
124 	pthread_create(&thread, NULL, tiny_race_func, NULL);
125 	global_var = 43;
126 	pthread_join(thread, NULL);
127 
128 	T_LOG("Done pthread global_var=%d", global_var);
129 
130 	// This always triggers a ThreadSanitizer data race thanks to the fixed seed
131 	fibers_log_level = FIBERS_LOG_INFO;
132 	fibers_may_yield_probability = 1;
133 	random_set_seed(1234);
134 
135 	fiber_t fiber = fibers_create(FIBERS_DEFAULT_STACK_SIZE, tiny_race_func, NULL);
136 	global_var = 43;
137 	fibers_join(fiber);
138 
139 	T_LOG("Done fibers global_var=%d", global_var);
140 	T_PASS("tsan_tiny_race");
141 }
142 
143 #define NUM_INCREMENTS 100000
144 #define NUM_THREADS 10
145 
146 struct inc_state {
147 	volatile int64_t counter;
148 	//_Atomic int64_t counter;
149 	lck_mtx_t mtx;
150 	lck_grp_t grp;
151 };
152 
153 void*
increment_counter(void * arg)154 increment_counter(void* arg)
155 {
156 	struct inc_state *s = (struct inc_state *)arg;
157 	for (int i = 0; i < NUM_INCREMENTS; i++) {
158 		// Remove locks to fail the test and trigger a ThreadSanitizer data race
159 		lck_mtx_lock(&s->mtx);
160 		//lck_mtx_lock_spin(&s->mtx);
161 		s->counter++;
162 		//os_atomic_inc(&s->counter, relaxed);
163 		lck_mtx_unlock(&s->mtx);
164 	}
165 	return NULL;
166 }
167 
168 T_DECL(mutex_mock_increment_int, "mutex mock test")
169 {
170 	// fibers_log_level = 1;
171 	// fibers_may_yield_probability = 0;
172 	random_set_seed(1234);
173 
174 	fiber_t mythreads[NUM_THREADS] = {};
175 	struct inc_state s = {.counter = 0};
176 	lck_grp_init(&s.grp, "test_mutex", LCK_GRP_ATTR_NULL);
177 	lck_mtx_init(&s.mtx, &s.grp, LCK_ATTR_NULL);
178 
179 	// Create fibers
180 	for (int i = 0; i < NUM_THREADS; i++) {
181 		mythreads[i] = fibers_create(FIBERS_DEFAULT_STACK_SIZE, increment_counter, (void*)&s);
182 	}
183 
184 	// Wait for all fibers to finish
185 	for (int i = 0; i < NUM_THREADS; i++) {
186 		fibers_join(mythreads[i]);
187 	}
188 	lck_mtx_destroy(&s.mtx, &s.grp);
189 
190 	T_LOG("Done counter=%lld", os_atomic_load(&s.counter, relaxed));
191 	T_ASSERT_EQ(s.counter, (int64_t)(NUM_INCREMENTS * NUM_THREADS), "race detected on counter");
192 
193 	T_PASS("mutex_mock_increment_int");
194 }
195