-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Run TLS destructors at process exit on all platforms #134085
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
Changes from all commits
4a2bfec
2494b97
60cdb6a
bfea6a3
5b6cc98
8e9f7bc
8f51622
3d640a9
cc68e0e
58220d7
82b363b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
use crate::mem; | ||
|
||
pub unsafe fn at_process_exit(cb: unsafe extern "C" fn()) { | ||
// Miri does not support atexit. | ||
#[cfg(not(miri))] | ||
assert_eq!(unsafe { libc::atexit(mem::transmute(cb)) }, 0); | ||
|
||
#[cfg(miri)] | ||
let _ = cb; | ||
} |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This could use a few more comments, explaining what enable_thread / enable_process are doing. The names sound like they enable a thread/process but that's not quite right I think. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,21 +3,40 @@ | |
//! that will run all native TLS destructors in the destructor list. | ||
|
||
use crate::ptr; | ||
use crate::sync::atomic::{AtomicBool, Ordering}; | ||
use crate::sys::thread_local::exit::at_process_exit; | ||
use crate::sys::thread_local::key::{LazyKey, set}; | ||
|
||
#[cfg(target_thread_local)] | ||
pub fn enable() { | ||
use crate::sys::thread_local::destructors; | ||
fn enable_thread() { | ||
static DTORS: LazyKey = LazyKey::new(Some(run_thread)); | ||
|
||
static DTORS: LazyKey = LazyKey::new(Some(run)); | ||
// Setting the key value to something other than NULL will result in the | ||
// destructor being run at thread exit. | ||
unsafe { | ||
set(DTORS.force(), ptr::without_provenance_mut(1)); | ||
} | ||
|
||
unsafe extern "C" fn run_thread(_: *mut u8) { | ||
run() | ||
} | ||
} | ||
|
||
// Setting the key value to something other than NULL will result in the | ||
// destructor being run at thread exit. | ||
unsafe { | ||
set(DTORS.force(), ptr::without_provenance_mut(1)); | ||
fn enable_process() { | ||
static REGISTERED: AtomicBool = AtomicBool::new(false); | ||
if !REGISTERED.swap(true, Ordering::Relaxed) { | ||
unsafe { at_process_exit(run_process) }; | ||
} | ||
|
||
unsafe extern "C" fn run_process() { | ||
run() | ||
} | ||
} | ||
|
||
unsafe extern "C" fn run(_: *mut u8) { | ||
fn run() { | ||
use crate::sys::thread_local::destructors; | ||
|
||
unsafe { | ||
destructors::run(); | ||
// On platforms with `__cxa_thread_atexit_impl`, `destructors::run` | ||
|
@@ -28,33 +47,55 @@ pub fn enable() { | |
crate::rt::thread_cleanup(); | ||
} | ||
} | ||
|
||
enable_thread(); | ||
enable_process(); | ||
} | ||
|
||
/// On platforms with key-based TLS, the system runs the destructors for us. | ||
/// We still have to make sure that [`crate::rt::thread_cleanup`] is called, | ||
/// however. This is done by defering the execution of a TLS destructor to | ||
/// the next round of destruction inside the TLS destructors. | ||
/// | ||
/// POSIX systems do not run TLS destructors at process exit. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are they guaranteed to not run? Or are they just not guaranteed to run? Does the implementation deal gracefully with implementations that run them anyway? |
||
/// Thus we register our own callback to invoke them in that case. | ||
#[cfg(not(target_thread_local))] | ||
pub fn enable() { | ||
const DEFER: *mut u8 = ptr::without_provenance_mut(1); | ||
const RUN: *mut u8 = ptr::without_provenance_mut(2); | ||
|
||
static CLEANUP: LazyKey = LazyKey::new(Some(run)); | ||
|
||
unsafe { set(CLEANUP.force(), DEFER) } | ||
|
||
unsafe extern "C" fn run(state: *mut u8) { | ||
if state == DEFER { | ||
// Make sure that this function is run again in the next round of | ||
// TLS destruction. If there is no futher round, there will be leaks, | ||
// but that's okay, `thread_cleanup` is not guaranteed to be called. | ||
unsafe { set(CLEANUP.force(), RUN) } | ||
} else { | ||
debug_assert_eq!(state, RUN); | ||
// If the state is still RUN in the next round of TLS destruction, | ||
// it means that no other TLS destructors defined by this runtime | ||
// have been run, as they would have set the state to DEFER. | ||
crate::rt::thread_cleanup(); | ||
fn enable_thread() { | ||
const DEFER: *mut u8 = ptr::without_provenance_mut(1); | ||
const RUN: *mut u8 = ptr::without_provenance_mut(2); | ||
|
||
static CLEANUP: LazyKey = LazyKey::new(Some(run_thread)); | ||
|
||
unsafe { set(CLEANUP.force(), DEFER) } | ||
|
||
unsafe extern "C" fn run_thread(state: *mut u8) { | ||
if state == DEFER { | ||
// Make sure that this function is run again in the next round of | ||
// TLS destruction. If there is no futher round, there will be leaks, | ||
// but that's okay, `thread_cleanup` is not guaranteed to be called. | ||
unsafe { set(CLEANUP.force(), RUN) } | ||
} else { | ||
debug_assert_eq!(state, RUN); | ||
// If the state is still RUN in the next round of TLS destruction, | ||
// it means that no other TLS destructors defined by this runtime | ||
// have been run, as they would have set the state to DEFER. | ||
crate::rt::thread_cleanup(); | ||
} | ||
} | ||
} | ||
|
||
fn enable_process() { | ||
static REGISTERED: AtomicBool = AtomicBool::new(false); | ||
if !REGISTERED.swap(true, Ordering::Relaxed) { | ||
unsafe { at_process_exit(run_process) }; | ||
} | ||
|
||
unsafe extern "C" fn run_process() { | ||
unsafe { crate::sys::thread_local::key::run_dtors() }; | ||
} | ||
} | ||
|
||
enable_thread(); | ||
enable_process(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
//! The platform has no threads, so we just need to register | ||
//! a process exit callback. | ||
|
||
use crate::cell::Cell; | ||
use crate::sys::thread_local::exit::at_process_exit; | ||
use crate::sys::thread_local::statik::run_dtors; | ||
|
||
pub fn enable() { | ||
struct Registered(Cell<bool>); | ||
// SAFETY: the target doesn't have threads. | ||
unsafe impl Sync for Registered {} | ||
|
||
static REGISTERED: Registered = Registered(Cell::new(false)); | ||
|
||
if !REGISTERED.0.get() { | ||
REGISTERED.0.set(true); | ||
unsafe { at_process_exit(run_process) }; | ||
} | ||
|
||
unsafe extern "C" fn run_process() { | ||
unsafe { run_dtors() }; | ||
crate::rt::thread_cleanup(); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you file an issue for this in the Miri repo? If std starts using this, we should consider supporting it.