mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-09 16:37:36 +00:00

The (unsafe) Mutex from sys_common had a rather complicated interface. You were supposed to call init() manually, unless you could guarantee it was neither moved nor used reentrantly. Calling `destroy()` was also optional, although it was unclear if 1) resources might be leaked or not, and 2) if destroy() should only be called when `init()` was called. This allowed for a number of interesting (confusing?) different ways to use this Mutex, all captured in a single type. In practice, this type was only ever used in two ways: 1. As a static variable. In this case, neither init() nor destroy() are called. The variable is never moved, and it is never used reentrantly. It is only ever locked using the LockGuard, never with raw_lock. 2. As a Boxed variable. In this case, both init() and destroy() are called, it will be moved and possibly used reentrantly. No other combinations are used anywhere in `std`. This change simplifies things by splitting this Mutex type into two types matching the two use cases: StaticMutex and MovableMutex. The interface of both new types is now both safer and simpler. The first one does not call nor expose init/destroy, and the second one calls those automatically in its new() and Drop functions. Also, the locking functions of MovableMutex are no longer unsafe.
75 lines
2.2 KiB
Rust
75 lines
2.2 KiB
Rust
//! Implementation of running at_exit routines
|
|
//!
|
|
//! Documentation can be found on the `rt::at_exit` function.
|
|
|
|
use crate::mem;
|
|
use crate::ptr;
|
|
use crate::sys_common::mutex::StaticMutex;
|
|
|
|
type Queue = Vec<Box<dyn FnOnce()>>;
|
|
|
|
// 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).
|
|
// It is UB to attempt to acquire this mutex reentrantly!
|
|
static LOCK: StaticMutex = StaticMutex::new();
|
|
static mut QUEUE: *mut Queue = ptr::null_mut();
|
|
|
|
const DONE: *mut Queue = 1_usize as *mut _;
|
|
|
|
// The maximum number of times the cleanup routines will be run. While running
|
|
// the at_exit closures new ones may be registered, and this count is the number
|
|
// of times the new closures will be allowed to register successfully. After
|
|
// this number of iterations all new registrations will return `false`.
|
|
const ITERS: usize = 10;
|
|
|
|
unsafe fn init() -> bool {
|
|
if QUEUE.is_null() {
|
|
let state: Box<Queue> = box Vec::new();
|
|
QUEUE = Box::into_raw(state);
|
|
} else if QUEUE == DONE {
|
|
// can't re-init after a cleanup
|
|
return false;
|
|
}
|
|
|
|
true
|
|
}
|
|
|
|
pub fn cleanup() {
|
|
for i in 1..=ITERS {
|
|
unsafe {
|
|
let queue = {
|
|
let _guard = LOCK.lock();
|
|
mem::replace(&mut QUEUE, if i == ITERS { DONE } else { ptr::null_mut() })
|
|
};
|
|
|
|
// make sure we're not recursively cleaning up
|
|
assert!(queue != DONE);
|
|
|
|
// If we never called init, not need to cleanup!
|
|
if !queue.is_null() {
|
|
let queue: Box<Queue> = Box::from_raw(queue);
|
|
for to_run in *queue {
|
|
// We are not holding any lock, so reentrancy is fine.
|
|
to_run();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn push(f: Box<dyn FnOnce()>) -> bool {
|
|
unsafe {
|
|
let _guard = LOCK.lock();
|
|
if init() {
|
|
// We are just moving `f` around, not calling it.
|
|
// There is no possibility of reentrancy here.
|
|
(*QUEUE).push(f);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|