Skip to content

Commit f010307

Browse files
committed
add seperate tls drop test for windows
1 parent 0f3766e commit f010307

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed

tests/pass/concurrency/tls_lib_drop.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//@ignore-target-windows: TLS destructor order is different on Windows.
2+
13
use std::cell::RefCell;
24
use std::thread;
35

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
//@only-target-windows: TLS destructor order is different on Windows.
2+
3+
use std::cell::RefCell;
4+
use std::thread;
5+
6+
struct TestCell {
7+
value: RefCell<u8>,
8+
}
9+
10+
impl Drop for TestCell {
11+
fn drop(&mut self) {
12+
for _ in 0..10 {
13+
thread::yield_now();
14+
}
15+
println!("Dropping: {} (should be before 'Continue main 1').", *self.value.borrow())
16+
}
17+
}
18+
19+
thread_local! {
20+
static A: TestCell = TestCell { value: RefCell::new(0) };
21+
static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
22+
}
23+
24+
/// Check that destructors of the library thread locals are executed immediately
25+
/// after a thread terminates.
26+
fn check_destructors() {
27+
thread::spawn(|| {
28+
A.with(|f| {
29+
assert_eq!(*f.value.borrow(), 0);
30+
*f.value.borrow_mut() = 5;
31+
});
32+
A_CONST.with(|f| {
33+
assert_eq!(*f.value.borrow(), 10);
34+
*f.value.borrow_mut() = 15;
35+
});
36+
})
37+
.join()
38+
.unwrap();
39+
println!("Continue main 1.")
40+
}
41+
42+
struct JoinCell {
43+
value: RefCell<Option<thread::JoinHandle<u8>>>,
44+
}
45+
46+
impl Drop for JoinCell {
47+
fn drop(&mut self) {
48+
for _ in 0..10 {
49+
thread::yield_now();
50+
}
51+
let join_handle = self.value.borrow_mut().take().unwrap();
52+
println!("Joining: {} (should be before 'Continue main 2').", join_handle.join().unwrap());
53+
}
54+
}
55+
56+
thread_local! {
57+
static B: JoinCell = JoinCell { value: RefCell::new(None) };
58+
}
59+
60+
/// Check that the destructor can be blocked joining another thread.
61+
fn check_blocking() {
62+
thread::spawn(|| {
63+
B.with(|f| {
64+
assert!(f.value.borrow().is_none());
65+
let handle = thread::spawn(|| 7);
66+
*f.value.borrow_mut() = Some(handle);
67+
});
68+
})
69+
.join()
70+
.unwrap();
71+
println!("Continue main 2.");
72+
// Preempt the main thread so that the destructor gets executed and can join
73+
// the thread.
74+
thread::yield_now();
75+
thread::yield_now();
76+
}
77+
78+
// This test tests that TLS destructors have run before the thread joins. The
79+
// test has no false positives (meaning: if the test fails, there's actually
80+
// an ordering problem). It may have false negatives, where the test passes but
81+
// join is not guaranteed to be after the TLS destructors. However, false
82+
// negatives should be exceedingly rare due to judicious use of
83+
// thread::yield_now and running the test several times.
84+
fn join_orders_after_tls_destructors() {
85+
use std::sync::atomic::{AtomicU8, Ordering};
86+
87+
// We emulate a synchronous MPSC rendezvous channel using only atomics and
88+
// thread::yield_now. We can't use std::mpsc as the implementation itself
89+
// may rely on thread locals.
90+
//
91+
// The basic state machine for an SPSC rendezvous channel is:
92+
// FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS
93+
// where the first transition is done by the “receiving” thread and the 2nd
94+
// transition is done by the “sending” thread.
95+
//
96+
// We add an additional state `THREAD2_LAUNCHED` between `FRESH` and
97+
// `THREAD1_WAITING` to block until all threads are actually running.
98+
//
99+
// A thread that joins on the “receiving” thread completion should never
100+
// observe the channel in the `THREAD1_WAITING` state. If this does occur,
101+
// we switch to the “poison” state `THREAD2_JOINED` and panic all around.
102+
// (This is equivalent to “sending” from an alternate producer thread.)
103+
const FRESH: u8 = 0;
104+
const THREAD2_LAUNCHED: u8 = 1;
105+
const THREAD1_WAITING: u8 = 2;
106+
const MAIN_THREAD_RENDEZVOUS: u8 = 3;
107+
const THREAD2_JOINED: u8 = 4;
108+
static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH);
109+
110+
for _ in 0..10 {
111+
SYNC_STATE.store(FRESH, Ordering::SeqCst);
112+
113+
let jh = thread::Builder::new()
114+
.name("thread1".into())
115+
.spawn(move || {
116+
struct TlDrop;
117+
118+
impl Drop for TlDrop {
119+
fn drop(&mut self) {
120+
let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst);
121+
loop {
122+
match sync_state {
123+
THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(),
124+
MAIN_THREAD_RENDEZVOUS => break,
125+
THREAD2_JOINED =>
126+
panic!(
127+
"Thread 1 still running after thread 2 joined on thread 1"
128+
),
129+
v => unreachable!("sync state: {}", v),
130+
}
131+
sync_state = SYNC_STATE.load(Ordering::SeqCst);
132+
}
133+
}
134+
}
135+
136+
thread_local! {
137+
static TL_DROP: TlDrop = TlDrop;
138+
}
139+
140+
TL_DROP.with(|_| {});
141+
142+
loop {
143+
match SYNC_STATE.load(Ordering::SeqCst) {
144+
FRESH => thread::yield_now(),
145+
THREAD2_LAUNCHED => break,
146+
v => unreachable!("sync state: {}", v),
147+
}
148+
}
149+
})
150+
.unwrap();
151+
152+
let jh2 = thread::Builder::new()
153+
.name("thread2".into())
154+
.spawn(move || {
155+
assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH);
156+
jh.join().unwrap();
157+
match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) {
158+
MAIN_THREAD_RENDEZVOUS => return,
159+
THREAD2_LAUNCHED | THREAD1_WAITING => {
160+
panic!("Thread 2 running after thread 1 join before main thread rendezvous")
161+
}
162+
v => unreachable!("sync state: {:?}", v),
163+
}
164+
})
165+
.unwrap();
166+
167+
loop {
168+
match SYNC_STATE.compare_exchange(
169+
THREAD1_WAITING,
170+
MAIN_THREAD_RENDEZVOUS,
171+
Ordering::SeqCst,
172+
Ordering::SeqCst,
173+
) {
174+
Ok(_) => break,
175+
Err(FRESH) => thread::yield_now(),
176+
Err(THREAD2_LAUNCHED) => thread::yield_now(),
177+
Err(THREAD2_JOINED) => {
178+
panic!("Main thread rendezvous after thread 2 joined thread 1")
179+
}
180+
v => unreachable!("sync state: {:?}", v),
181+
}
182+
}
183+
jh2.join().unwrap();
184+
}
185+
}
186+
187+
fn main() {
188+
check_destructors();
189+
check_blocking();
190+
join_orders_after_tls_destructors();
191+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Dropping: 15 (should be before 'Continue main 1').
2+
Dropping: 5 (should be before 'Continue main 1').
3+
Continue main 1.
4+
Joining: 7 (should be before 'Continue main 2').
5+
Continue main 2.

0 commit comments

Comments
 (0)