Skip to content

notes on TLS dtor order #2503

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions src/shims/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,8 @@ impl<'tcx> TlsData<'tcx> {
/// and the thread has a non-NULL value associated with that key,
/// the value of the key is set to NULL, and then the function pointed
/// to is called with the previously associated value as its sole argument.
/// The order of destructor calls is unspecified if more than one destructor
/// exists for a thread when it exits.
/// **The order of destructor calls is unspecified if more than one destructor
/// exists for a thread when it exits.**
///
/// If, after all the destructors have been called for all non-NULL values
/// with associated destructors, there are still some non-NULL values with
Expand All @@ -188,6 +188,13 @@ impl<'tcx> TlsData<'tcx> {
Some(key) => Excluded(key),
None => Unbounded,
};
// We interpret the documentaion above (taken from POSIX) as saying that we need to iterate
// over all keys and run each destructor at least once before running any destructor a 2nd
// time. That's why we have `key` to indicate how far we got in the current iteration. If we
// return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to
// start the next round.
// TODO: In the future, we might consider randomizing destructor order, but we still have to
// uphold this requirement.
for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
match data.entry(thread_id) {
BTreeEntry::Occupied(entry) => {
Expand Down
10 changes: 6 additions & 4 deletions tests/pass/concurrency/tls_lib_drop.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//@ignore-target-windows: TLS destructor order is different on Windows.

use std::cell::RefCell;
use std::thread;

Expand All @@ -24,14 +22,18 @@ thread_local! {
/// Check that destructors of the library thread locals are executed immediately
/// after a thread terminates.
fn check_destructors() {
// 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
// `__cxa_thread_atexit_impl`), and Miri on Windows.
thread::spawn(|| {
A.with(|f| {
assert_eq!(*f.value.borrow(), 0);
*f.value.borrow_mut() = 5;
*f.value.borrow_mut() = 8;
});
A_CONST.with(|f| {
assert_eq!(*f.value.borrow(), 10);
*f.value.borrow_mut() = 15;
*f.value.borrow_mut() = 8;
});
})
.join()
Expand Down
4 changes: 2 additions & 2 deletions tests/pass/concurrency/tls_lib_drop.stdout
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
Dropping: 5 (should be before 'Continue main 1').
Dropping: 15 (should be before 'Continue main 1').
Dropping: 8 (should be before 'Continue main 1').
Dropping: 8 (should be before 'Continue main 1').
Continue main 1.
Joining: 7 (should be before 'Continue main 2').
Continue main 2.
191 changes: 0 additions & 191 deletions tests/pass/concurrency/tls_lib_drop_windows.rs

This file was deleted.

5 changes: 0 additions & 5 deletions tests/pass/concurrency/tls_lib_drop_windows.stdout

This file was deleted.

7 changes: 7 additions & 0 deletions tests/pass/concurrency/tls_pthread_drop_order.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
//@ignore-target-windows: No libc on Windows
//! Test that pthread_key destructors are run in the right order.
//! Note that these are *not* used by actual `thread_local!` on Linux! Those use
//! `thread_local_dtor::register_dtor` from the stdlib instead. In Miri this hits the fallback path
//! in `register_dtor_fallback`, which uses a *single* pthread_key to manage a thread-local list of
//! dtors to call.

use std::mem;
use std::ptr;
Expand Down Expand Up @@ -44,6 +49,8 @@ unsafe extern "C" fn dtor(ptr: *mut u64) {
// If the record is wrong, the cannary will never get cleared, leading to a leak -> test fails.
// If the record is incomplete (i.e., more dtor calls happen), the check at the beginning of this function will fail -> test fails.
// The correct sequence is: First key 0, then key 1, then key 0.
// Note that this relies on dtor order, which is not specified by POSIX, but seems to be
// consistent between Miri and Linux currently (as of Aug 2022).
if RECORD == 0_1_0 {
drop(Box::from_raw(CANNARY));
CANNARY = ptr::null_mut();
Expand Down