Auto merge of #84115 - CDirkx:rt, r=m-ou-se

Rework `init` and `cleanup`

This PR reworks the code in `std` that runs before and after `main` and centralizes this code respectively in the functions `init` and `cleanup` in both `sys_common` and `sys`. This makes is easy to see what code is executed during initialization and cleanup on each platform just by looking at e.g. `sys::windows::init`.

Full list of changes:
- new module `rt` in `sys_common` to contain `init` and `cleanup` and the runtime macros.
- `at_exit` and the mechanism to register exit handlers has been completely removed. In practice this was only used for closing sockets on windows and flushing stdout, which have been moved to `cleanup`.
- <s>On windows `alloc` and `net` initialization is now done in `init`, this saves a runtime check in every allocation and network use.</s>
This commit is contained in:
bors 2021-04-25 04:45:39 +00:00
commit 5da10c0121
26 changed files with 176 additions and 263 deletions

View File

@ -293,6 +293,10 @@ mod util;
const DEFAULT_BUF_SIZE: usize = crate::sys_common::io::DEFAULT_BUF_SIZE;
pub(crate) fn cleanup() {
stdio::cleanup()
}
struct Guard<'a> {
buf: &'a mut Vec<u8>,
len: usize,

View File

@ -13,7 +13,6 @@ use crate::pin::Pin;
use crate::sync::atomic::{AtomicBool, Ordering};
use crate::sync::{Arc, Mutex, MutexGuard};
use crate::sys::stdio;
use crate::sys_common;
use crate::sys_common::remutex::{ReentrantMutex, ReentrantMutexGuard};
type LocalStream = Arc<Mutex<Vec<u8>>>;
@ -508,6 +507,8 @@ pub struct StdoutLock<'a> {
inner: ReentrantMutexGuard<'a, RefCell<LineWriter<StdoutRaw>>>,
}
static STDOUT: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> = SyncOnceCell::new();
/// Constructs a new handle to the standard output of the current process.
///
/// Each handle returned is a reference to a shared global buffer whose access
@ -549,34 +550,28 @@ pub struct StdoutLock<'a> {
/// ```
#[stable(feature = "rust1", since = "1.0.0")]
pub fn stdout() -> Stdout {
static INSTANCE: SyncOnceCell<ReentrantMutex<RefCell<LineWriter<StdoutRaw>>>> =
SyncOnceCell::new();
fn cleanup() {
if let Some(instance) = INSTANCE.get() {
// Flush the data and disable buffering during shutdown
// by replacing the line writer by one with zero
// buffering capacity.
// We use try_lock() instead of lock(), because someone
// might have leaked a StdoutLock, which would
// otherwise cause a deadlock here.
if let Some(lock) = Pin::static_ref(instance).try_lock() {
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
}
}
}
Stdout {
inner: Pin::static_ref(&INSTANCE).get_or_init_pin(
|| unsafe {
let _ = sys_common::at_exit(cleanup);
ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw())))
},
inner: Pin::static_ref(&STDOUT).get_or_init_pin(
|| unsafe { ReentrantMutex::new(RefCell::new(LineWriter::new(stdout_raw()))) },
|mutex| unsafe { mutex.init() },
),
}
}
pub fn cleanup() {
if let Some(instance) = STDOUT.get() {
// Flush the data and disable buffering during shutdown
// by replacing the line writer by one with zero
// buffering capacity.
// We use try_lock() instead of lock(), because someone
// might have leaked a StdoutLock, which would
// otherwise cause a deadlock here.
if let Some(lock) = Pin::static_ref(instance).try_lock() {
*lock.borrow_mut() = LineWriter::with_capacity(0, stdout_raw());
}
}
}
impl Stdout {
/// Locks this handle to the standard output stream, returning a writable
/// guard.

View File

@ -1749,7 +1749,7 @@ impl Child {
/// [platform-specific behavior]: #platform-specific-behavior
#[stable(feature = "rust1", since = "1.0.0")]
pub fn exit(code: i32) -> ! {
crate::sys_common::cleanup();
crate::sys_common::rt::cleanup();
crate::sys::os::exit(code)
}

View File

@ -26,33 +26,16 @@ fn lang_start_internal(
argv: *const *const u8,
) -> isize {
use crate::panic;
use crate::sys;
use crate::sys_common;
use crate::sys_common::thread_info;
use crate::thread::Thread;
sys::init();
// SAFETY: Only called once during runtime initialization.
unsafe { sys_common::rt::init(argc, argv) };
unsafe {
let main_guard = sys::thread::guard::init();
sys::stack_overflow::init();
let exit_code = panic::catch_unwind(main);
// Next, set up the current Thread with the guard information we just
// created. Note that this isn't necessary in general for new threads,
// but we just do this to name the main thread and to give it correct
// info about the stack bounds.
let thread = Thread::new(Some("main".to_owned()));
thread_info::set(main_guard, thread);
sys_common::rt::cleanup();
// Store our args if necessary in a squirreled away location
sys::args::init(argc, argv);
// Let's run some code!
let exit_code = panic::catch_unwind(main);
sys_common::cleanup();
exit_code.unwrap_or(101) as isize
}
exit_code.unwrap_or(101) as isize
}
#[cfg(not(test))]

View File

@ -37,7 +37,6 @@ pub mod pipe;
#[path = "../unsupported/process.rs"]
pub mod process;
pub mod rwlock;
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
pub mod thread_local_dtor;
@ -96,9 +95,17 @@ pub extern "C" fn __rust_abort() {
abort_internal();
}
#[cfg(not(test))]
pub fn init() {
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(argc: isize, argv: *const *const u8) {
let _ = net::init();
args::init(argc, argv);
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {
args::cleanup();
}
#[cfg(not(test))]

View File

@ -1,5 +0,0 @@
#[inline]
pub unsafe fn init() {}
#[inline]
pub unsafe fn cleanup() {}

View File

@ -23,8 +23,6 @@ pub unsafe fn init(argc: isize, argv: *const *const u8) {
}
}
pub unsafe fn cleanup() {}
pub fn args() -> Args {
let args = unsafe { (ARGS.load(Ordering::Relaxed) as *const ArgsStore).as_ref() };
if let Some(args) = args { Args(args.iter()) } else { Args([].iter()) }

View File

@ -32,7 +32,6 @@ pub mod pipe;
#[path = "../unsupported/process.rs"]
pub mod process;
pub mod rwlock;
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
pub mod thread_local_key;
@ -40,8 +39,17 @@ pub mod time;
pub use crate::sys_common::os_str_bytes as os_str;
#[cfg(not(test))]
pub fn init() {}
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(argc: isize, argv: *const *const u8) {
unsafe {
args::init(argc, argv);
}
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {}
/// This function is used to implement functionality that simply doesn't exist.
/// Programs relying on this functionality will need to deal with the error.

View File

@ -1,4 +0,0 @@
#[cfg_attr(test, allow(dead_code))]
pub unsafe fn init() {}
pub unsafe fn cleanup() {}

View File

@ -44,14 +44,13 @@ pub mod time;
pub use crate::sys_common::os_str_bytes as os_str;
#[cfg(not(test))]
pub fn init() {
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(argc: isize, argv: *const *const u8) {
// The standard streams might be closed on application startup. To prevent
// std::io::{stdin, stdout,stderr} objects from using other unrelated file
// resources opened later, we reopen standards streams when they are closed.
unsafe {
sanitize_standard_fds();
}
sanitize_standard_fds();
// By default, some platforms will send a *signal* when an EPIPE error
// would otherwise be delivered. This runtime doesn't install a SIGPIPE
@ -60,26 +59,24 @@ pub fn init() {
//
// Hence, we set SIGPIPE to ignore when the program starts up in order
// to prevent this problem.
unsafe {
reset_sigpipe();
}
reset_sigpipe();
cfg_if::cfg_if! {
if #[cfg(miri)] {
// The standard fds are always available in Miri.
unsafe fn sanitize_standard_fds() {}
} else if #[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "vxworks",
// The poll on Darwin doesn't set POLLNVAL for closed fds.
target_os = "macos",
target_os = "ios",
target_os = "redox",
)))] {
// In the case when all file descriptors are open, the poll has been
// observed to perform better than fcntl (on GNU/Linux).
unsafe fn sanitize_standard_fds() {
stack_overflow::init();
args::init(argc, argv);
unsafe fn sanitize_standard_fds() {
#[cfg(not(miri))]
// The standard fds are always available in Miri.
cfg_if::cfg_if! {
if #[cfg(not(any(
target_os = "emscripten",
target_os = "fuchsia",
target_os = "vxworks",
// The poll on Darwin doesn't set POLLNVAL for closed fds.
target_os = "macos",
target_os = "ios",
target_os = "redox",
)))] {
use crate::sys::os::errno;
let pfds: &mut [_] = &mut [
libc::pollfd { fd: 0, events: 0, revents: 0 },
@ -104,9 +101,7 @@ pub fn init() {
libc::abort();
}
}
}
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] {
unsafe fn sanitize_standard_fds() {
} else if #[cfg(any(target_os = "macos", target_os = "ios", target_os = "redox"))] {
use crate::sys::os::errno;
for fd in 0..3 {
if libc::fcntl(fd, libc::F_GETFD) == -1 && errno() == libc::EBADF {
@ -116,17 +111,20 @@ pub fn init() {
}
}
}
} else {
unsafe fn sanitize_standard_fds() {}
}
}
#[cfg(not(any(target_os = "emscripten", target_os = "fuchsia")))]
unsafe fn reset_sigpipe() {
#[cfg(not(any(target_os = "emscripten", target_os = "fuchsia")))]
assert!(signal(libc::SIGPIPE, libc::SIG_IGN) != libc::SIG_ERR);
}
#[cfg(any(target_os = "emscripten", target_os = "fuchsia"))]
unsafe fn reset_sigpipe() {}
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {
args::cleanup();
stack_overflow::cleanup();
}
#[cfg(target_os = "android")]

View File

@ -1,8 +1,5 @@
use crate::ffi::OsString;
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
pub unsafe fn cleanup() {}
pub struct Args {}
pub fn args() -> Args {

View File

@ -10,8 +10,13 @@ pub use crate::sys_common::os_str_bytes as os_str;
// spec definition?
use crate::os::raw::c_char;
#[cfg(not(test))]
pub fn init() {}
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {}
pub fn unsupported<T>() -> std_io::Result<T> {
Err(unsupported_err())

View File

@ -15,7 +15,6 @@ pub mod path;
pub mod pipe;
pub mod process;
pub mod rwlock;
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
#[cfg(target_thread_local)]

View File

@ -1,3 +0,0 @@
pub unsafe fn init() {}
pub unsafe fn cleanup() {}

View File

@ -5,10 +5,6 @@ use crate::fmt;
use crate::os::wasi::ffi::OsStrExt;
use crate::vec;
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
pub unsafe fn cleanup() {}
pub struct Args {
iter: vec::IntoIter<OsString>,
}

View File

@ -42,8 +42,6 @@ pub mod pipe;
pub mod process;
#[path = "../unsupported/rwlock.rs"]
pub mod rwlock;
#[path = "../unsupported/stack_overflow.rs"]
pub mod stack_overflow;
pub mod stdio;
pub mod thread;
#[path = "../unsupported/thread_local_dtor.rs"]

View File

@ -2,12 +2,6 @@ use crate::ffi::OsString;
use crate::fmt;
use crate::vec;
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
// On wasm these should always be null, so there's nothing for us to do here
}
pub unsafe fn cleanup() {}
pub fn args() -> Args {
Args { iter: Vec::new().into_iter() }
}

View File

@ -35,8 +35,6 @@ pub mod path;
pub mod pipe;
#[path = "../unsupported/process.rs"]
pub mod process;
#[path = "../unsupported/stack_overflow.rs"]
pub mod stack_overflow;
#[path = "../unsupported/stdio.rs"]
pub mod stdio;
pub mod thread;

View File

@ -14,10 +14,6 @@ use crate::vec;
use core::iter;
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {}
pub unsafe fn cleanup() {}
pub fn args() -> Args {
unsafe {
let lp_cmd_line = c::GetCommandLineW();

View File

@ -49,8 +49,17 @@ cfg_if::cfg_if! {
}
}
#[cfg(not(test))]
pub fn init() {}
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
pub unsafe fn init(_argc: isize, _argv: *const *const u8) {
stack_overflow::init();
}
// SAFETY: must be called only once during runtime cleanup.
// NOTE: this is not guaranteed to run, for example when the program aborts.
pub unsafe fn cleanup() {
net::cleanup();
}
pub fn decode_error_kind(errno: i32) -> ErrorKind {
match errno as c::DWORD {

View File

@ -9,7 +9,7 @@ use crate::sync::Once;
use crate::sys;
use crate::sys::c;
use crate::sys_common::net;
use crate::sys_common::{self, AsInner, FromInner, IntoInner};
use crate::sys_common::{AsInner, FromInner, IntoInner};
use crate::time::Duration;
use libc::{c_int, c_long, c_ulong, c_void};
@ -26,25 +26,30 @@ pub mod netc {
pub struct Socket(c::SOCKET);
static INIT: Once = Once::new();
/// Checks whether the Windows socket interface has been started already, and
/// if not, starts it.
pub fn init() {
static START: Once = Once::new();
START.call_once(|| unsafe {
INIT.call_once(|| unsafe {
let mut data: c::WSADATA = mem::zeroed();
let ret = c::WSAStartup(
0x202, // version 2.2
&mut data,
);
assert_eq!(ret, 0);
let _ = sys_common::at_exit(|| {
c::WSACleanup();
});
});
}
pub fn cleanup() {
if INIT.is_completed() {
// only close the socket interface if it has actually been started
unsafe {
c::WSACleanup();
}
}
}
/// Returns the last error from the Windows socket interface.
fn last_error() -> io::Error {
io::Error::from_raw_os_error(unsafe { c::WSAGetLastError() })

View File

@ -37,5 +37,3 @@ pub unsafe fn init() {
// Set the thread stack guarantee for the main thread.
let _h = Handler::new();
}
pub unsafe fn cleanup() {}

View File

@ -9,5 +9,3 @@ impl Handler {
}
pub unsafe fn init() {}
pub unsafe fn cleanup() {}

View File

@ -1,74 +0,0 @@
//! 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
}
}
}

View File

@ -20,35 +20,6 @@
#[cfg(test)]
mod tests;
use crate::sync::Once;
use crate::sys;
macro_rules! rtabort {
($($t:tt)*) => (crate::sys_common::util::abort(format_args!($($t)*)))
}
macro_rules! rtassert {
($e:expr) => {
if !$e {
rtabort!(concat!("assertion failed: ", stringify!($e)));
}
};
}
#[allow(unused_macros)] // not used on all platforms
macro_rules! rtunwrap {
($ok:ident, $e:expr) => {
match $e {
$ok(v) => v,
ref err => {
let err = err.as_ref().map(drop); // map Ok/Some which might not be Debug
rtabort!(concat!("unwrap failed: ", stringify!($e), " = {:?}"), err)
}
}
};
}
pub mod at_exit_imp;
pub mod backtrace;
pub mod bytestring;
pub mod condvar;
@ -61,6 +32,8 @@ pub mod mutex;
pub mod os_str_bytes;
pub mod process;
pub mod remutex;
#[macro_use]
pub mod rt;
pub mod rwlock;
pub mod thread;
pub mod thread_info;
@ -108,30 +81,6 @@ pub trait FromInner<Inner> {
fn from_inner(inner: Inner) -> Self;
}
/// Enqueues a procedure to run when the main thread exits.
///
/// Currently these closures are only run once the main *Rust* thread exits.
/// Once the `at_exit` handlers begin running, more may be enqueued, but not
/// infinitely so. Eventually a handler registration will be forced to fail.
///
/// Returns `Ok` if the handler was successfully registered, meaning that the
/// closure will be run once the main thread exits. Returns `Err` to indicate
/// that the closure could not be registered, meaning that it is not scheduled
/// to be run.
pub fn at_exit<F: FnOnce() + Send + 'static>(f: F) -> Result<(), ()> {
if at_exit_imp::push(Box::new(f)) { Ok(()) } else { Err(()) }
}
/// One-time runtime cleanup.
pub fn cleanup() {
static CLEANUP: Once = Once::new();
CLEANUP.call_once(|| unsafe {
sys::args::cleanup();
sys::stack_overflow::cleanup();
at_exit_imp::cleanup();
});
}
// Computes (value*numer)/denom without overflow, as long as both
// (numer*denom) and the overall result fit into i64 (which is the case
// for our time conversions).

View File

@ -0,0 +1,64 @@
#![deny(unsafe_op_in_unsafe_fn)]
use crate::sync::Once;
use crate::sys;
use crate::sys_common::thread_info;
use crate::thread::Thread;
// One-time runtime initialization.
// Runs before `main`.
// SAFETY: must be called only once during runtime initialization.
// NOTE: this is not guaranteed to run, for example when Rust code is called externally.
#[cfg_attr(test, allow(dead_code))]
pub unsafe fn init(argc: isize, argv: *const *const u8) {
unsafe {
sys::init(argc, argv);
let main_guard = sys::thread::guard::init();
// Next, set up the current Thread with the guard information we just
// created. Note that this isn't necessary in general for new threads,
// but we just do this to name the main thread and to give it correct
// info about the stack bounds.
let thread = Thread::new(Some("main".to_owned()));
thread_info::set(main_guard, thread);
}
}
// One-time runtime cleanup.
// Runs after `main` or at program exit.
// NOTE: this is not guaranteed to run, for example when the program aborts.
#[cfg_attr(test, allow(dead_code))]
pub fn cleanup() {
static CLEANUP: Once = Once::new();
CLEANUP.call_once(|| unsafe {
// Flush stdout and disable buffering.
crate::io::cleanup();
// SAFETY: Only called once during runtime cleanup.
sys::cleanup();
});
}
macro_rules! rtabort {
($($t:tt)*) => (crate::sys_common::util::abort(format_args!($($t)*)))
}
macro_rules! rtassert {
($e:expr) => {
if !$e {
rtabort!(concat!("assertion failed: ", stringify!($e)));
}
};
}
#[allow(unused_macros)] // not used on all platforms
macro_rules! rtunwrap {
($ok:ident, $e:expr) => {
match $e {
$ok(v) => v,
ref err => {
let err = err.as_ref().map(drop); // map Ok/Some which might not be Debug
rtabort!(concat!("unwrap failed: ", stringify!($e), " = {:?}"), err)
}
}
};
}