tests for when a thread-local gets initialized in a tls dtor

This commit is contained in:
Ralf Jung 2024-06-24 08:43:16 +02:00
parent aded2be375
commit f071a205c7
6 changed files with 97 additions and 64 deletions

View file

@ -4,27 +4,28 @@
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() {
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())
}
}
// Test both regular and `const` thread-locals.
thread_local! {
static A: TestCell = TestCell { value: RefCell::new(0) };
static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
}
// We use the same value for both of them, since destructor order differs between Miri on Linux
// (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a
// thread-local linked list of dtors to call), real Linux rustc (which uses
@ -44,26 +45,29 @@ fn check_destructors() {
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() {
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) };
}
thread::spawn(|| {
B.with(|f| {
assert!(f.value.borrow().is_none());
@ -74,10 +78,36 @@ fn check_blocking() {
.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();
}
fn check_tls_init_in_dtor() {
struct Bar;
impl Drop for Bar {
fn drop(&mut self) {
println!("Bar dtor (should be before `Continue main 3`).");
}
}
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo dtor (should be before `Bar dtor`).");
// We initialize another thread-local inside the dtor, which is an interesting corner case.
thread_local!(static BAR: Bar = Bar);
BAR.with(|_| {});
}
}
thread_local!(static FOO: Foo = Foo);
thread::spawn(|| {
FOO.with(|_| {});
})
.join()
.unwrap();
println!("Continue main 3.");
}
// This test tests that TLS destructors have run before the thread joins. The
@ -248,6 +278,8 @@ fn dtors_in_dtors_in_dtors() {
fn main() {
check_destructors();
check_blocking();
check_tls_init_in_dtor();
join_orders_after_tls_destructors();
dtors_in_dtors_in_dtors();
}

View file

@ -3,3 +3,6 @@ Dropping: 8 (should be before 'Continue main 1').
Continue main 1.
Joining: 7 (should be before 'Continue main 2').
Continue main 2.
Foo dtor (should be before `Bar dtor`).
Bar dtor (should be before `Continue main 3`).
Continue main 3.

View file

@ -3,3 +3,6 @@ Dropping: 8 (should be before 'Continue main 1').
Continue main 1.
Joining: 7 (should be before 'Continue main 2').
Continue main 2.
Foo dtor (should be before `Bar dtor`).
Bar dtor (should be before `Continue main 3`).
Continue main 3.

View file

@ -1,31 +1,27 @@
//! Check that destructors of the thread locals are executed on all OSes
//! (even when we do not support concurrency, and cannot run the other test).
//! Check that destructors of main thread thread locals are executed.
use std::cell::RefCell;
struct Bar;
struct TestCell {
value: RefCell<u8>,
}
impl Drop for TestCell {
impl Drop for Bar {
fn drop(&mut self) {
eprintln!("Dropping: {}", *self.value.borrow())
println!("Bar dtor");
}
}
thread_local! {
static A: TestCell = TestCell { value: RefCell::new(0) };
static A_CONST: TestCell = const { TestCell { value: RefCell::new(10) } };
struct Foo;
impl Drop for Foo {
fn drop(&mut self) {
println!("Foo dtor");
// We initialize another thread-local inside the dtor, which is an interesting corner case.
// Also we use a `const` thread-local here, just to also have that code path covered.
thread_local!(static BAR: Bar = const { Bar });
BAR.with(|_| {});
}
}
thread_local!(static FOO: Foo = Foo);
fn main() {
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() = 5; // Same value as above since the drop order is different on different platforms
});
eprintln!("Continue main.")
FOO.with(|_| {});
}

View file

@ -1,3 +0,0 @@
Continue main.
Dropping: 5
Dropping: 5

View file

@ -0,0 +1,2 @@
Foo dtor
Bar dtor