Skip to content

Commit e0f0e1f

Browse files
committed
Auto merge of #2503 - RalfJung:tls-dtor-order, r=RalfJung
notes on TLS dtor order Fixes #2486
2 parents a109994 + afacf62 commit e0f0e1f

File tree

6 files changed

+24
-204
lines changed

6 files changed

+24
-204
lines changed

src/shims/tls.rs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,8 +165,8 @@ impl<'tcx> TlsData<'tcx> {
165165
/// and the thread has a non-NULL value associated with that key,
166166
/// the value of the key is set to NULL, and then the function pointed
167167
/// to is called with the previously associated value as its sole argument.
168-
/// The order of destructor calls is unspecified if more than one destructor
169-
/// exists for a thread when it exits.
168+
/// **The order of destructor calls is unspecified if more than one destructor
169+
/// exists for a thread when it exits.**
170170
///
171171
/// If, after all the destructors have been called for all non-NULL values
172172
/// with associated destructors, there are still some non-NULL values with
@@ -188,6 +188,13 @@ impl<'tcx> TlsData<'tcx> {
188188
Some(key) => Excluded(key),
189189
None => Unbounded,
190190
};
191+
// We interpret the documentaion above (taken from POSIX) as saying that we need to iterate
192+
// over all keys and run each destructor at least once before running any destructor a 2nd
193+
// time. That's why we have `key` to indicate how far we got in the current iteration. If we
194+
// return `None`, `schedule_next_pthread_tls_dtor` will re-try with `ket` set to `None` to
195+
// start the next round.
196+
// TODO: In the future, we might consider randomizing destructor order, but we still have to
197+
// uphold this requirement.
191198
for (&key, TlsEntry { data, dtor }) in thread_local.range_mut((start, Unbounded)) {
192199
match data.entry(thread_id) {
193200
BTreeEntry::Occupied(entry) => {

tests/pass/concurrency/tls_lib_drop.rs

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

@@ -24,14 +22,18 @@ thread_local! {
2422
/// Check that destructors of the library thread locals are executed immediately
2523
/// after a thread terminates.
2624
fn check_destructors() {
25+
// We use the same value for both of them, since destructor order differs between Miri on Linux
26+
// (which uses `register_dtor_fallback`, in the end using a single pthread_key to manage a
27+
// thread-local linked list of dtors to call), real Linux rustc (which uses
28+
// `__cxa_thread_atexit_impl`), and Miri on Windows.
2729
thread::spawn(|| {
2830
A.with(|f| {
2931
assert_eq!(*f.value.borrow(), 0);
30-
*f.value.borrow_mut() = 5;
32+
*f.value.borrow_mut() = 8;
3133
});
3234
A_CONST.with(|f| {
3335
assert_eq!(*f.value.borrow(), 10);
34-
*f.value.borrow_mut() = 15;
36+
*f.value.borrow_mut() = 8;
3537
});
3638
})
3739
.join()
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
Dropping: 5 (should be before 'Continue main 1').
2-
Dropping: 15 (should be before 'Continue main 1').
1+
Dropping: 8 (should be before 'Continue main 1').
2+
Dropping: 8 (should be before 'Continue main 1').
33
Continue main 1.
44
Joining: 7 (should be before 'Continue main 2').
55
Continue main 2.

tests/pass/concurrency/tls_lib_drop_windows.rs

Lines changed: 0 additions & 191 deletions
This file was deleted.

tests/pass/concurrency/tls_lib_drop_windows.stdout

Lines changed: 0 additions & 5 deletions
This file was deleted.

tests/pass/concurrency/tls_pthread_drop_order.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
//@ignore-target-windows: No libc on Windows
2+
//! Test that pthread_key destructors are run in the right order.
3+
//! Note that these are *not* used by actual `thread_local!` on Linux! Those use
4+
//! `thread_local_dtor::register_dtor` from the stdlib instead. In Miri this hits the fallback path
5+
//! in `register_dtor_fallback`, which uses a *single* pthread_key to manage a thread-local list of
6+
//! dtors to call.
27
38
use std::mem;
49
use std::ptr;
@@ -44,6 +49,8 @@ unsafe extern "C" fn dtor(ptr: *mut u64) {
4449
// If the record is wrong, the cannary will never get cleared, leading to a leak -> test fails.
4550
// If the record is incomplete (i.e., more dtor calls happen), the check at the beginning of this function will fail -> test fails.
4651
// The correct sequence is: First key 0, then key 1, then key 0.
52+
// Note that this relies on dtor order, which is not specified by POSIX, but seems to be
53+
// consistent between Miri and Linux currently (as of Aug 2022).
4754
if RECORD == 0_1_0 {
4855
drop(Box::from_raw(CANNARY));
4956
CANNARY = ptr::null_mut();

0 commit comments

Comments
 (0)