std: Lower abstractions for thread_local/at_exit

The current implementations use `std::sync` primitives, but these primitives
currently end up relying on `thread_info` and a local `Thread` being available
(mainly for checking the panicking flag).

To get around this, this commit lowers the abstractions used by the windows
thread_local implementation as well as the at_exit_imp module. Both of these
modules now use a `sys::Mutex` and a `static mut` and manage the
allocation/locking manually.
This commit is contained in:
Alex Crichton 2014-12-17 14:37:38 -08:00 committed by Aaron Turon
parent a27fbac868
commit 5759cff48e
2 changed files with 78 additions and 46 deletions

View File

@ -16,35 +16,49 @@ use core::prelude::*;
use boxed::Box;
use vec::Vec;
use sync::{Mutex, atomic, Once, ONCE_INIT};
use mem;
use thunk::Thunk;
use sys_common::mutex::{Mutex, MUTEX_INIT};
type Queue = Mutex<Vec<Thunk>>;
type Queue = Vec<Thunk>;
static INIT: Once = ONCE_INIT;
static QUEUE: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT;
// NB these are specifically not types from `std::sync` as they currently rely
// on poisoning and this module needs to operate at a lower level than requiring
// the thread infrastructure to be in place (useful on the borders of
// initialization/destruction).
static LOCK: Mutex = MUTEX_INIT;
static mut QUEUE: *mut Queue = 0 as *mut Queue;
fn init() {
let state: Box<Queue> = box Mutex::new(Vec::new());
unsafe {
QUEUE.store(mem::transmute(state), atomic::SeqCst);
// FIXME: switch this to use atexit as below. Currently this
// segfaults (the queue's memory is mysteriously gone), so
// instead the cleanup is tied to the `std::rt` entry point.
//
// ::libc::atexit(cleanup);
unsafe fn init() {
if QUEUE.is_null() {
let state: Box<Queue> = box Vec::new();
QUEUE = mem::transmute(state);
} else {
// can't re-init after a cleanup
rtassert!(QUEUE as uint != 1);
}
// FIXME: switch this to use atexit as below. Currently this
// segfaults (the queue's memory is mysteriously gone), so
// instead the cleanup is tied to the `std::rt` entry point.
//
// ::libc::atexit(cleanup);
}
pub fn cleanup() {
unsafe {
let queue = QUEUE.swap(0, atomic::SeqCst);
if queue != 0 {
LOCK.lock();
let queue = QUEUE;
QUEUE = 1 as *mut _;
LOCK.unlock();
// make sure we're not recursively cleaning up
rtassert!(queue as uint != 1);
// If we never called init, not need to cleanup!
if queue as uint != 0 {
let queue: Box<Queue> = mem::transmute(queue);
let v = mem::replace(&mut *queue.lock(), Vec::new());
for to_run in v.into_iter() {
for to_run in queue.into_iter() {
to_run.invoke(());
}
}
@ -52,14 +66,10 @@ pub fn cleanup() {
}
pub fn push(f: Thunk) {
INIT.doit(init);
unsafe {
// Note that the check against 0 for the queue pointer is not atomic at
// all with respect to `run`, meaning that this could theoretically be a
// use-after-free. There's not much we can do to protect against that,
// however. Let's just assume a well-behaved runtime and go from there!
let queue = QUEUE.load(atomic::SeqCst);
rtassert!(queue != 0);
(*(queue as *const Queue)).lock().push(f);
LOCK.lock();
init();
(*QUEUE).push(f);
LOCK.unlock();
}
}

View File

@ -14,7 +14,7 @@ use libc::types::os::arch::extra::{DWORD, LPVOID, BOOL};
use mem;
use rt;
use sync::{ONCE_INIT, Once, Mutex};
use sys_common::mutex::{MUTEX_INIT, Mutex};
pub type Key = DWORD;
pub type Dtor = unsafe extern fn(*mut u8);
@ -53,8 +53,12 @@ pub type Dtor = unsafe extern fn(*mut u8);
// [2]: https://github.com/ChromiumWebApps/chromium/blob/master/base
// /threading/thread_local_storage_win.cc#L42
static INIT_DTORS: Once = ONCE_INIT;
static mut DTORS: *mut Mutex<Vec<(Key, Dtor)>> = 0 as *mut _;
// NB these are specifically not types from `std::sync` as they currently rely
// on poisoning and this module needs to operate at a lower level than requiring
// the thread infrastructure to be in place (useful on the borders of
// initialization/destruction).
static DTOR_LOCK: Mutex = MUTEX_INIT;
static mut DTORS: *mut Vec<(Key, Dtor)> = 0 as *mut _;
// -------------------------------------------------------------------------
// Native bindings
@ -124,30 +128,40 @@ extern "system" {
//
// FIXME: This could probably be at least a little faster with a BTree.
fn init_dtors() {
let dtors = box Mutex::new(Vec::<(Key, Dtor)>::new());
unsafe {
DTORS = mem::transmute(dtors);
}
unsafe fn init_dtors() {
if !DTORS.is_null() { return }
rt::at_exit(move|| unsafe {
mem::transmute::<_, Box<Mutex<Vec<(Key, Dtor)>>>>(DTORS);
let dtors = box Vec::<(Key, Dtor)>::new();
DTORS = mem::transmute(dtors);
rt::at_exit(move|| {
DTOR_LOCK.lock();
let dtors = DTORS;
DTORS = 0 as *mut _;
mem::transmute::<_, Box<Vec<(Key, Dtor)>>>(dtors);
assert!(DTORS.is_null()); // can't re-init after destructing
DTOR_LOCK.unlock();
});
}
unsafe fn register_dtor(key: Key, dtor: Dtor) {
INIT_DTORS.doit(init_dtors);
let mut dtors = (*DTORS).lock();
dtors.push((key, dtor));
DTOR_LOCK.lock();
init_dtors();
(*DTORS).push((key, dtor));
DTOR_LOCK.unlock();
}
unsafe fn unregister_dtor(key: Key) -> bool {
if DTORS.is_null() { return false }
let mut dtors = (*DTORS).lock();
let before = dtors.len();
dtors.retain(|&(k, _)| k != key);
dtors.len() != before
DTOR_LOCK.lock();
init_dtors();
let ret = {
let dtors = &mut *DTORS;
let before = dtors.len();
dtors.retain(|&(k, _)| k != key);
dtors.len() != before
};
DTOR_LOCK.unlock();
ret
}
// -------------------------------------------------------------------------
@ -219,12 +233,20 @@ unsafe extern "system" fn on_tls_callback(h: LPVOID,
}
unsafe fn run_dtors() {
if DTORS.is_null() { return }
let mut any_run = true;
for _ in range(0, 5i) {
if !any_run { break }
any_run = false;
let dtors = (*DTORS).lock().iter().map(|p| *p).collect::<Vec<_>>();
let dtors = {
DTOR_LOCK.lock();
let ret = if DTORS.is_null() {
Vec::new()
} else {
(*DTORS).iter().map(|s| *s).collect()
};
DTOR_LOCK.unlock();
ret
};
for &(key, dtor) in dtors.iter() {
let ptr = TlsGetValue(key);
if !ptr.is_null() {