1 //! Tests that make sure accessing thread-locals while exiting the thread doesn't cause panics.
2 
3 #![cfg(not(miri))] // Miri detects that this test is buggy: the destructor of `FOO` uses `std::thread::current()`!
4 
5 use std::thread;
6 use std::time::Duration;
7 
8 use crossbeam_channel::{select, unbounded};
9 use crossbeam_utils::thread::scope;
10 
ms(ms: u64) -> Duration11 fn ms(ms: u64) -> Duration {
12     Duration::from_millis(ms)
13 }
14 
15 #[test]
16 #[cfg_attr(target_os = "macos", ignore = "TLS is destroyed too early on macOS")]
use_while_exiting()17 fn use_while_exiting() {
18     struct Foo;
19 
20     impl Drop for Foo {
21         fn drop(&mut self) {
22             // A blocking operation after the thread-locals have been dropped. This will attempt to
23             // use the thread-locals and must not panic.
24             let (_s, r) = unbounded::<()>();
25             select! {
26                 recv(r) -> _ => {}
27                 default(ms(100)) => {}
28             }
29         }
30     }
31 
32     thread_local! {
33         static FOO: Foo = const { Foo };
34     }
35 
36     let (s, r) = unbounded::<()>();
37 
38     scope(|scope| {
39         scope.spawn(|_| {
40             // First initialize `FOO`, then the thread-locals related to crossbeam-channel.
41             FOO.with(|_| ());
42             r.recv().unwrap();
43             // At thread exit, thread-locals related to crossbeam-channel get dropped first and
44             // `FOO` is dropped last.
45         });
46 
47         scope.spawn(|_| {
48             thread::sleep(ms(100));
49             s.send(()).unwrap();
50         });
51     })
52     .unwrap();
53 }
54