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