Skip to content

Commit 61f5680

Browse files
committed
add stdlib test for TLS dtor order
1 parent ad576d8 commit 61f5680

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

tests/pass/concurrency/tls_lib_drop.rs

+109
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,116 @@ fn check_blocking() {
7171
thread::yield_now();
7272
}
7373

74+
// This test tests that TLS destructors have run before the thread joins. The
75+
// test has no false positives (meaning: if the test fails, there's actually
76+
// an ordering problem). It may have false negatives, where the test passes but
77+
// join is not guaranteed to be after the TLS destructors. However, false
78+
// negatives should be exceedingly rare due to judicious use of
79+
// thread::yield_now and running the test several times.
80+
fn join_orders_after_tls_destructors() {
81+
use std::sync::atomic::{AtomicU8, Ordering};
82+
83+
// We emulate a synchronous MPSC rendezvous channel using only atomics and
84+
// thread::yield_now. We can't use std::mpsc as the implementation itself
85+
// may rely on thread locals.
86+
//
87+
// The basic state machine for an SPSC rendezvous channel is:
88+
// FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS
89+
// where the first transition is done by the “receiving” thread and the 2nd
90+
// transition is done by the “sending” thread.
91+
//
92+
// We add an additional state `THREAD2_LAUNCHED` between `FRESH` and
93+
// `THREAD1_WAITING` to block until all threads are actually running.
94+
//
95+
// A thread that joins on the “receiving” thread completion should never
96+
// observe the channel in the `THREAD1_WAITING` state. If this does occur,
97+
// we switch to the “poison” state `THREAD2_JOINED` and panic all around.
98+
// (This is equivalent to “sending” from an alternate producer thread.)
99+
const FRESH: u8 = 0;
100+
const THREAD2_LAUNCHED: u8 = 1;
101+
const THREAD1_WAITING: u8 = 2;
102+
const MAIN_THREAD_RENDEZVOUS: u8 = 3;
103+
const THREAD2_JOINED: u8 = 4;
104+
static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH);
105+
106+
for _ in 0..10 {
107+
SYNC_STATE.store(FRESH, Ordering::SeqCst);
108+
109+
let jh = thread::Builder::new()
110+
.name("thread1".into())
111+
.spawn(move || {
112+
struct TlDrop;
113+
114+
impl Drop for TlDrop {
115+
fn drop(&mut self) {
116+
let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst);
117+
loop {
118+
match sync_state {
119+
THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(),
120+
MAIN_THREAD_RENDEZVOUS => break,
121+
THREAD2_JOINED => panic!(
122+
"Thread 1 still running after thread 2 joined on thread 1"
123+
),
124+
v => unreachable!("sync state: {}", v),
125+
}
126+
sync_state = SYNC_STATE.load(Ordering::SeqCst);
127+
}
128+
}
129+
}
130+
131+
thread_local! {
132+
static TL_DROP: TlDrop = TlDrop;
133+
}
134+
135+
TL_DROP.with(|_| {});
136+
137+
loop {
138+
match SYNC_STATE.load(Ordering::SeqCst) {
139+
FRESH => thread::yield_now(),
140+
THREAD2_LAUNCHED => break,
141+
v => unreachable!("sync state: {}", v),
142+
}
143+
}
144+
})
145+
.unwrap();
146+
147+
let jh2 = thread::Builder::new()
148+
.name("thread2".into())
149+
.spawn(move || {
150+
assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH);
151+
jh.join().unwrap();
152+
match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) {
153+
MAIN_THREAD_RENDEZVOUS => return,
154+
THREAD2_LAUNCHED | THREAD1_WAITING => {
155+
panic!("Thread 2 running after thread 1 join before main thread rendezvous")
156+
}
157+
v => unreachable!("sync state: {:?}", v),
158+
}
159+
})
160+
.unwrap();
161+
162+
loop {
163+
match SYNC_STATE.compare_exchange(
164+
THREAD1_WAITING,
165+
MAIN_THREAD_RENDEZVOUS,
166+
Ordering::SeqCst,
167+
Ordering::SeqCst,
168+
) {
169+
Ok(_) => break,
170+
Err(FRESH) => thread::yield_now(),
171+
Err(THREAD2_LAUNCHED) => thread::yield_now(),
172+
Err(THREAD2_JOINED) => {
173+
panic!("Main thread rendezvous after thread 2 joined thread 1")
174+
}
175+
v => unreachable!("sync state: {:?}", v),
176+
}
177+
}
178+
jh2.join().unwrap();
179+
}
180+
}
181+
74182
fn main() {
75183
check_destructors();
76184
check_blocking();
185+
join_orders_after_tls_destructors();
77186
}

0 commit comments

Comments
 (0)