1 //@only-target-linux
2 //@compile-flags: -Zmiri-disable-isolation
3
4 use std::mem::MaybeUninit;
5 use std::ptr;
6 use std::sync::atomic::AtomicI32;
7 use std::sync::atomic::Ordering;
8 use std::thread;
9 use std::time::{Duration, Instant};
10
wake_nobody()11 fn wake_nobody() {
12 let futex = 0;
13
14 // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting.
15 unsafe {
16 assert_eq!(libc::syscall(libc::SYS_futex, &futex as *const i32, libc::FUTEX_WAKE, 1), 0);
17 }
18
19 // Same, but without omitting the unused arguments.
20 unsafe {
21 assert_eq!(
22 libc::syscall(
23 libc::SYS_futex,
24 &futex as *const i32,
25 libc::FUTEX_WAKE,
26 1,
27 ptr::null::<libc::timespec>(),
28 0usize,
29 0,
30 ),
31 0,
32 );
33 }
34 }
35
wake_dangling()36 fn wake_dangling() {
37 let futex = Box::new(0);
38 let ptr: *const i32 = &*futex;
39 drop(futex);
40
41 // Wake 1 waiter. Expect zero waiters woken up, as nobody is waiting.
42 unsafe {
43 assert_eq!(libc::syscall(libc::SYS_futex, ptr, libc::FUTEX_WAKE, 1), 0);
44 }
45 }
46
wait_wrong_val()47 fn wait_wrong_val() {
48 let futex: i32 = 123;
49
50 // Only wait if the futex value is 456.
51 unsafe {
52 assert_eq!(
53 libc::syscall(
54 libc::SYS_futex,
55 &futex as *const i32,
56 libc::FUTEX_WAIT,
57 456,
58 ptr::null::<libc::timespec>(),
59 ),
60 -1,
61 );
62 assert_eq!(*libc::__errno_location(), libc::EAGAIN);
63 }
64 }
65
wait_timeout()66 fn wait_timeout() {
67 let start = Instant::now();
68
69 let futex: i32 = 123;
70
71 // Wait for 200ms, with nobody waking us up early.
72 unsafe {
73 assert_eq!(
74 libc::syscall(
75 libc::SYS_futex,
76 &futex as *const i32,
77 libc::FUTEX_WAIT,
78 123,
79 &libc::timespec { tv_sec: 0, tv_nsec: 200_000_000 },
80 ),
81 -1,
82 );
83 assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT);
84 }
85
86 assert!((200..1000).contains(&start.elapsed().as_millis()));
87 }
88
wait_absolute_timeout()89 fn wait_absolute_timeout() {
90 let start = Instant::now();
91
92 // Get the current monotonic timestamp as timespec.
93 let mut timeout = unsafe {
94 let mut now: MaybeUninit<libc::timespec> = MaybeUninit::uninit();
95 assert_eq!(libc::clock_gettime(libc::CLOCK_MONOTONIC, now.as_mut_ptr()), 0);
96 now.assume_init()
97 };
98
99 // Add 200ms.
100 timeout.tv_nsec += 200_000_000;
101 if timeout.tv_nsec > 1_000_000_000 {
102 timeout.tv_nsec -= 1_000_000_000;
103 timeout.tv_sec += 1;
104 }
105
106 let futex: i32 = 123;
107
108 // Wait for 200ms from now, with nobody waking us up early.
109 unsafe {
110 assert_eq!(
111 libc::syscall(
112 libc::SYS_futex,
113 &futex as *const i32,
114 libc::FUTEX_WAIT_BITSET,
115 123,
116 &timeout,
117 0usize,
118 u32::MAX,
119 ),
120 -1,
121 );
122 assert_eq!(*libc::__errno_location(), libc::ETIMEDOUT);
123 }
124
125 assert!((200..1000).contains(&start.elapsed().as_millis()));
126 }
127
wait_wake()128 fn wait_wake() {
129 let start = Instant::now();
130
131 static mut FUTEX: i32 = 0;
132
133 let t = thread::spawn(move || {
134 thread::sleep(Duration::from_millis(200));
135 unsafe {
136 assert_eq!(
137 libc::syscall(
138 libc::SYS_futex,
139 &FUTEX as *const i32,
140 libc::FUTEX_WAKE,
141 10, // Wake up at most 10 threads.
142 ),
143 1, // Woken up one thread.
144 );
145 }
146 });
147
148 unsafe {
149 assert_eq!(
150 libc::syscall(
151 libc::SYS_futex,
152 &FUTEX as *const i32,
153 libc::FUTEX_WAIT,
154 0,
155 ptr::null::<libc::timespec>(),
156 ),
157 0,
158 );
159 }
160
161 assert!((200..1000).contains(&start.elapsed().as_millis()));
162 t.join().unwrap();
163 }
164
wait_wake_bitset()165 fn wait_wake_bitset() {
166 let start = Instant::now();
167
168 static mut FUTEX: i32 = 0;
169
170 let t = thread::spawn(move || {
171 thread::sleep(Duration::from_millis(200));
172 unsafe {
173 assert_eq!(
174 libc::syscall(
175 libc::SYS_futex,
176 &FUTEX as *const i32,
177 libc::FUTEX_WAKE_BITSET,
178 10, // Wake up at most 10 threads.
179 ptr::null::<libc::timespec>(),
180 0usize,
181 0b1001, // bitset
182 ),
183 0, // Didn't match any thread.
184 );
185 }
186 thread::sleep(Duration::from_millis(200));
187 unsafe {
188 assert_eq!(
189 libc::syscall(
190 libc::SYS_futex,
191 &FUTEX as *const i32,
192 libc::FUTEX_WAKE_BITSET,
193 10, // Wake up at most 10 threads.
194 ptr::null::<libc::timespec>(),
195 0usize,
196 0b0110, // bitset
197 ),
198 1, // Woken up one thread.
199 );
200 }
201 });
202
203 unsafe {
204 assert_eq!(
205 libc::syscall(
206 libc::SYS_futex,
207 &FUTEX as *const i32,
208 libc::FUTEX_WAIT_BITSET,
209 0,
210 ptr::null::<libc::timespec>(),
211 0usize,
212 0b0100, // bitset
213 ),
214 0,
215 );
216 }
217
218 assert!((400..1000).contains(&start.elapsed().as_millis()));
219 t.join().unwrap();
220 }
221
concurrent_wait_wake()222 fn concurrent_wait_wake() {
223 const FREE: i32 = 0;
224 const HELD: i32 = 1;
225
226 static FUTEX: AtomicI32 = AtomicI32::new(0);
227 static mut DATA: i32 = 0;
228 static WOKEN: AtomicI32 = AtomicI32::new(0);
229
230 let rounds = 50;
231 for _ in 0..rounds {
232 unsafe { DATA = 0 }; // Reset
233 // Suppose the main thread is holding a lock implemented using futex...
234 FUTEX.store(HELD, Ordering::Relaxed);
235
236 let t = thread::spawn(move || {
237 // If this syscall runs first, then we'll be woken up by
238 // the main thread's FUTEX_WAKE, and all is fine.
239 //
240 // If this sycall runs after the main thread's store
241 // and FUTEX_WAKE, the syscall must observe that
242 // the FUTEX is FREE != HELD and return without waiting
243 // or we'll deadlock.
244 unsafe {
245 let ret = libc::syscall(
246 libc::SYS_futex,
247 &FUTEX as *const AtomicI32,
248 libc::FUTEX_WAIT,
249 HELD,
250 ptr::null::<libc::timespec>(),
251 );
252 if ret == 0 {
253 // We actually slept. And then woke up again. So we should be ordered-after
254 // what happened-before the FUTEX_WAKE. So this is not a race.
255 assert_eq!(DATA, 1);
256 // Also remember that this happened at least once.
257 WOKEN.fetch_add(1, Ordering::Relaxed);
258 }
259 }
260 });
261 // Increase the chance that the other thread actually goes to sleep.
262 // (5 yields in a loop seem to make that happen around 40% of the time.)
263 for _ in 0..5 {
264 thread::yield_now();
265 }
266
267 FUTEX.store(FREE, Ordering::Relaxed);
268 unsafe {
269 DATA = 1;
270 libc::syscall(libc::SYS_futex, &FUTEX as *const AtomicI32, libc::FUTEX_WAKE, 1);
271 }
272
273 t.join().unwrap();
274 }
275
276 // Make sure we got the interesting case (of having woken a thread) at least once, but not *each* time.
277 let woken = WOKEN.load(Ordering::Relaxed);
278 //eprintln!("waking happened {woken} times");
279 assert!(woken > 0 && woken < rounds);
280 }
281
main()282 fn main() {
283 wake_nobody();
284 wake_dangling();
285 wait_wrong_val();
286 wait_timeout();
287 wait_absolute_timeout();
288 wait_wake();
289 wait_wake_bitset();
290 concurrent_wait_wake();
291 }
292