-
Notifications
You must be signed in to change notification settings - Fork 329
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add seperate tls drop test for windows
- Loading branch information
1 parent
6d4040f
commit 8a6c1f8
Showing
3 changed files
with
198 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,191 @@ | ||
//@only-target-windows: TLS destructor order is different on Windows. | ||
|
||
use std::cell::RefCell; | ||
use std::thread; | ||
|
||
struct TestCell { | ||
value: RefCell<u8>, | ||
} | ||
|
||
impl Drop for TestCell { | ||
fn drop(&mut self) { | ||
for _ in 0..10 { | ||
thread::yield_now(); | ||
} | ||
println!("Dropping: {} (should be before 'Continue main 1').", *self.value.borrow()) | ||
} | ||
} | ||
|
||
thread_local! { | ||
static A: TestCell = TestCell { value: RefCell::new(0) }; | ||
static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } }; | ||
} | ||
|
||
/// Check that destructors of the library thread locals are executed immediately | ||
/// after a thread terminates. | ||
fn check_destructors() { | ||
thread::spawn(|| { | ||
A.with(|f| { | ||
assert_eq!(*f.value.borrow(), 0); | ||
*f.value.borrow_mut() = 5; | ||
}); | ||
A_CONST.with(|f| { | ||
assert_eq!(*f.value.borrow(), 10); | ||
*f.value.borrow_mut() = 15; | ||
}); | ||
}) | ||
.join() | ||
.unwrap(); | ||
println!("Continue main 1.") | ||
} | ||
|
||
struct JoinCell { | ||
value: RefCell<Option<thread::JoinHandle<u8>>>, | ||
} | ||
|
||
impl Drop for JoinCell { | ||
fn drop(&mut self) { | ||
for _ in 0..10 { | ||
thread::yield_now(); | ||
} | ||
let join_handle = self.value.borrow_mut().take().unwrap(); | ||
println!("Joining: {} (should be before 'Continue main 2').", join_handle.join().unwrap()); | ||
} | ||
} | ||
|
||
thread_local! { | ||
static B: JoinCell = JoinCell { value: RefCell::new(None) }; | ||
} | ||
|
||
/// Check that the destructor can be blocked joining another thread. | ||
fn check_blocking() { | ||
thread::spawn(|| { | ||
B.with(|f| { | ||
assert!(f.value.borrow().is_none()); | ||
let handle = thread::spawn(|| 7); | ||
*f.value.borrow_mut() = Some(handle); | ||
}); | ||
}) | ||
.join() | ||
.unwrap(); | ||
println!("Continue main 2."); | ||
// Preempt the main thread so that the destructor gets executed and can join | ||
// the thread. | ||
thread::yield_now(); | ||
thread::yield_now(); | ||
} | ||
|
||
// This test tests that TLS destructors have run before the thread joins. The | ||
// test has no false positives (meaning: if the test fails, there's actually | ||
// an ordering problem). It may have false negatives, where the test passes but | ||
// join is not guaranteed to be after the TLS destructors. However, false | ||
// negatives should be exceedingly rare due to judicious use of | ||
// thread::yield_now and running the test several times. | ||
fn join_orders_after_tls_destructors() { | ||
use std::sync::atomic::{AtomicU8, Ordering}; | ||
|
||
// We emulate a synchronous MPSC rendezvous channel using only atomics and | ||
// thread::yield_now. We can't use std::mpsc as the implementation itself | ||
// may rely on thread locals. | ||
// | ||
// The basic state machine for an SPSC rendezvous channel is: | ||
// FRESH -> THREAD1_WAITING -> MAIN_THREAD_RENDEZVOUS | ||
// where the first transition is done by the “receiving” thread and the 2nd | ||
// transition is done by the “sending” thread. | ||
// | ||
// We add an additional state `THREAD2_LAUNCHED` between `FRESH` and | ||
// `THREAD1_WAITING` to block until all threads are actually running. | ||
// | ||
// A thread that joins on the “receiving” thread completion should never | ||
// observe the channel in the `THREAD1_WAITING` state. If this does occur, | ||
// we switch to the “poison” state `THREAD2_JOINED` and panic all around. | ||
// (This is equivalent to “sending” from an alternate producer thread.) | ||
const FRESH: u8 = 0; | ||
const THREAD2_LAUNCHED: u8 = 1; | ||
const THREAD1_WAITING: u8 = 2; | ||
const MAIN_THREAD_RENDEZVOUS: u8 = 3; | ||
const THREAD2_JOINED: u8 = 4; | ||
static SYNC_STATE: AtomicU8 = AtomicU8::new(FRESH); | ||
|
||
for _ in 0..10 { | ||
SYNC_STATE.store(FRESH, Ordering::SeqCst); | ||
|
||
let jh = thread::Builder::new() | ||
.name("thread1".into()) | ||
.spawn(move || { | ||
struct TlDrop; | ||
|
||
impl Drop for TlDrop { | ||
fn drop(&mut self) { | ||
let mut sync_state = SYNC_STATE.swap(THREAD1_WAITING, Ordering::SeqCst); | ||
loop { | ||
match sync_state { | ||
THREAD2_LAUNCHED | THREAD1_WAITING => thread::yield_now(), | ||
MAIN_THREAD_RENDEZVOUS => break, | ||
THREAD2_JOINED => | ||
panic!( | ||
"Thread 1 still running after thread 2 joined on thread 1" | ||
), | ||
v => unreachable!("sync state: {}", v), | ||
} | ||
sync_state = SYNC_STATE.load(Ordering::SeqCst); | ||
} | ||
} | ||
} | ||
|
||
thread_local! { | ||
static TL_DROP: TlDrop = TlDrop; | ||
} | ||
|
||
TL_DROP.with(|_| {}); | ||
|
||
loop { | ||
match SYNC_STATE.load(Ordering::SeqCst) { | ||
FRESH => thread::yield_now(), | ||
THREAD2_LAUNCHED => break, | ||
v => unreachable!("sync state: {}", v), | ||
} | ||
} | ||
}) | ||
.unwrap(); | ||
|
||
let jh2 = thread::Builder::new() | ||
.name("thread2".into()) | ||
.spawn(move || { | ||
assert_eq!(SYNC_STATE.swap(THREAD2_LAUNCHED, Ordering::SeqCst), FRESH); | ||
jh.join().unwrap(); | ||
match SYNC_STATE.swap(THREAD2_JOINED, Ordering::SeqCst) { | ||
MAIN_THREAD_RENDEZVOUS => return, | ||
THREAD2_LAUNCHED | THREAD1_WAITING => { | ||
panic!("Thread 2 running after thread 1 join before main thread rendezvous") | ||
} | ||
v => unreachable!("sync state: {:?}", v), | ||
} | ||
}) | ||
.unwrap(); | ||
|
||
loop { | ||
match SYNC_STATE.compare_exchange( | ||
THREAD1_WAITING, | ||
MAIN_THREAD_RENDEZVOUS, | ||
Ordering::SeqCst, | ||
Ordering::SeqCst, | ||
) { | ||
Ok(_) => break, | ||
Err(FRESH) => thread::yield_now(), | ||
Err(THREAD2_LAUNCHED) => thread::yield_now(), | ||
Err(THREAD2_JOINED) => { | ||
panic!("Main thread rendezvous after thread 2 joined thread 1") | ||
} | ||
v => unreachable!("sync state: {:?}", v), | ||
} | ||
} | ||
jh2.join().unwrap(); | ||
} | ||
} | ||
|
||
fn main() { | ||
check_destructors(); | ||
check_blocking(); | ||
join_orders_after_tls_destructors(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
Dropping: 15 (should be before 'Continue main 1'). | ||
Dropping: 5 (should be before 'Continue main 1'). | ||
Continue main 1. | ||
Joining: 7 (should be before 'Continue main 2'). | ||
Continue main 2. |