Skip to content

Commit

Permalink
std: Tweak rt::at_exit behavior
Browse files Browse the repository at this point in the history
There have been some recent panics on the bots and this commit is an attempt to
appease them. Previously it was considered invalid to run `rt::at_exit` after
the handlers had already started running. Due to the multithreaded nature of
applications, however, it is not always possible to guarantee this. For example
[this program][ex] will show off the abort.

[ex]: https://gist.github.com/alexcrichton/56300b87af6fa554e52d

The semantics of the `rt::at_exit` function have been modified as such:

* It is now legal to call `rt::at_exit` at any time. The return value now
  indicates whether the closure was successfully registered or not. Callers must
  now decide what to do with this information.
* The `rt::at_exit` handlers will now be run for a fixed number of iterations.
  Common cases (such as the example shown) may end up registering a new handler
  while others are running perhaps once or twice, so this common condition is
  covered by re-running the handlers a fixed number of times, after which new
  registrations are forbidden.

Some usage of `rt::at_exit` was updated to handle these new semantics, but
deprecated or unstable libraries calling `rt::at_exit` were not updated.
  • Loading branch information
alexcrichton committed Mar 21, 2015
1 parent ecf8c64 commit 1ec9adc
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 46 deletions.
2 changes: 1 addition & 1 deletion src/liblog/lib.rs
Expand Up @@ -443,7 +443,7 @@ fn init() {
DIRECTIVES = boxed::into_raw(box directives);

// Schedule the cleanup for the globals for when the runtime exits.
rt::at_exit(move || {
let _ = rt::at_exit(move || {
let _g = LOCK.lock();
assert!(!DIRECTIVES.is_null());
let _directives = Box::from_raw(DIRECTIVES);
Expand Down
24 changes: 16 additions & 8 deletions src/libstd/io/lazy.rs
Expand Up @@ -35,25 +35,33 @@ impl<T: Send + Sync + 'static> Lazy<T> {
pub fn get(&'static self) -> Option<Arc<T>> {
let _g = self.lock.lock();
unsafe {
let mut ptr = *self.ptr.get();
let ptr = *self.ptr.get();
if ptr.is_null() {
ptr = boxed::into_raw(self.init());
*self.ptr.get() = ptr;
Some(self.init())
} else if ptr as usize == 1 {
return None
None
} else {
Some((*ptr).clone())
}
Some((*ptr).clone())
}
}

fn init(&'static self) -> Box<Arc<T>> {
rt::at_exit(move || unsafe {
unsafe fn init(&'static self) -> Arc<T> {
// If we successfully register an at exit handler, then we cache the
// `Arc` allocation in our own internal box (it will get deallocated by
// the at exit handler). Otherwise we just return the freshly allocated
// `Arc`.
let registered = rt::at_exit(move || {
let g = self.lock.lock();
let ptr = *self.ptr.get();
*self.ptr.get() = 1 as *mut _;
drop(g);
drop(Box::from_raw(ptr))
});
Box::new((self.init)())
let ret = (self.init)();
if registered.is_ok() {
*self.ptr.get() = boxed::into_raw(Box::new(ret.clone()));
}
return ret
}
}
2 changes: 1 addition & 1 deletion src/libstd/old_io/stdio.rs
Expand Up @@ -238,7 +238,7 @@ pub fn stdin() -> StdinReader {
STDIN = boxed::into_raw(box stdin);

// Make sure to free it at exit
rt::at_exit(|| {
let _ = rt::at_exit(|| {
Box::from_raw(STDIN);
STDIN = ptr::null_mut();
});
Expand Down
59 changes: 36 additions & 23 deletions src/libstd/rt/at_exit_imp.rs
Expand Up @@ -12,6 +12,10 @@
//!
//! Documentation can be found on the `rt::at_exit` function.

// FIXME: switch this to use atexit. Currently this
// segfaults (the queue's memory is mysteriously gone), so
// instead the cleanup is tied to the `std::rt` entry point.

use boxed;
use boxed::Box;
use vec::Vec;
Expand All @@ -27,47 +31,56 @@ type Queue = Vec<Thunk<'static>>;
static LOCK: Mutex = MUTEX_INIT;
static mut QUEUE: *mut Queue = 0 as *mut Queue;

unsafe fn init() {
// 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 = boxed::into_raw(state);
} else {
} else if QUEUE as usize == 1 {
// can't re-init after a cleanup
rtassert!(QUEUE as uint != 1);
return false
}

// 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);
return true
}

pub fn cleanup() {
unsafe {
LOCK.lock();
let queue = QUEUE;
QUEUE = 1 as *mut _;
LOCK.unlock();
for i in 0..ITERS {
unsafe {
LOCK.lock();
let queue = QUEUE;
QUEUE = if i == ITERS - 1 {1} else {0} as *mut _;
LOCK.unlock();

// make sure we're not recursively cleaning up
rtassert!(queue as uint != 1);
// make sure we're not recursively cleaning up
rtassert!(queue as usize != 1);

// If we never called init, not need to cleanup!
if queue as uint != 0 {
let queue: Box<Queue> = Box::from_raw(queue);
for to_run in *queue {
to_run.invoke(());
// If we never called init, not need to cleanup!
if queue as usize != 0 {
let queue: Box<Queue> = Box::from_raw(queue);
for to_run in *queue {
to_run.invoke(());
}
}
}
}
}

pub fn push(f: Thunk<'static>) {
pub fn push(f: Thunk<'static>) -> bool {
let mut ret = true;
unsafe {
LOCK.lock();
init();
(*QUEUE).push(f);
if init() {
(*QUEUE).push(f);
} else {
ret = false;
}
LOCK.unlock();
}
return ret
}
22 changes: 10 additions & 12 deletions src/libstd/rt/mod.rs
Expand Up @@ -17,14 +17,9 @@
//! time being.

#![unstable(feature = "std_misc")]

// FIXME: this should not be here.
#![allow(missing_docs)]

#![allow(dead_code)]

use marker::Send;
use ops::FnOnce;
use prelude::v1::*;
use sys;
use thunk::Thunk;
use usize;
Expand Down Expand Up @@ -149,13 +144,16 @@ fn lang_start(main: *const u8, argc: int, argv: *const *const u8) -> int {

/// Enqueues a procedure to run when the main thread exits.
///
/// It is forbidden for procedures to register more `at_exit` handlers when they
/// are running, and doing so will lead to a process abort.
/// 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.
///
/// Note that other threads may still be running when `at_exit` routines start
/// running.
pub fn at_exit<F: FnOnce() + Send + 'static>(f: F) {
at_exit_imp::push(Thunk::new(f));
/// 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 rune.
pub fn at_exit<F: FnOnce() + Send + 'static>(f: F) -> Result<(), ()> {
if at_exit_imp::push(Thunk::new(f)) {Ok(())} else {Err(())}
}

/// One-time runtime cleanup.
Expand Down
2 changes: 1 addition & 1 deletion src/libstd/sys/common/helper_thread.rs
Expand Up @@ -112,7 +112,7 @@ impl<M: Send> Helper<M> {
self.cond.notify_one()
});

rt::at_exit(move || { self.shutdown() });
let _ = rt::at_exit(move || { self.shutdown() });
*self.initialized.get() = true;
} else if *self.chan.get() as uint == 1 {
panic!("cannot continue usage after shutdown");
Expand Down

0 comments on commit 1ec9adc

Please sign in to comment.