auto merge of #14644 : alexcrichton/rust/more-no-runtime-use-cases, r=brson
A few notable improvements were implemented to cut down on the number of aborts triggered by the standard library when a local task is not found. * Primarily, the unwinding functionality was restructured to support an unsafe top-level function, `try`. This function invokes a closure, capturing any failure which occurs inside of it. The purpose of this function is to be as lightweight of a "try block" as possible for rust, intended for use when the runtime is difficult to set up. This function is *not* meant to be used by normal rust code, nor should it be consider for use with normal rust code. * When invoking spawn(), a `fail!()` is triggered rather than an abort. * When invoking LocalIo::borrow(), which is transitively called by all I/O constructors, None is returned rather than aborting to indicate that there is no local I/O implementation. A test case was also added showing the variety of things that you can do without a runtime or task set up now. In general, this is just a refactoring to abort less quickly in the standard library when a local task is not found.
This commit is contained in:
commit
57e7147f3e
5 changed files with 282 additions and 161 deletions
|
@ -96,22 +96,24 @@ pub type Map = Vec<Option<(*u8, TLSValue, uint)>>;
|
|||
type TLSValue = Box<LocalData:Send>;
|
||||
|
||||
// Gets the map from the runtime. Lazily initialises if not done so already.
|
||||
unsafe fn get_local_map() -> &mut Map {
|
||||
unsafe fn get_local_map() -> Option<&mut Map> {
|
||||
use rt::local::Local;
|
||||
|
||||
if !Local::exists(None::<Task>) { return None }
|
||||
|
||||
let task: *mut Task = Local::unsafe_borrow();
|
||||
match &mut (*task).storage {
|
||||
// If the at_exit function is already set, then we just need to take
|
||||
// a loan out on the TLS map stored inside
|
||||
&LocalStorage(Some(ref mut map_ptr)) => {
|
||||
return map_ptr;
|
||||
return Some(map_ptr);
|
||||
}
|
||||
// If this is the first time we've accessed TLS, perform similar
|
||||
// actions to the oldsched way of doing things.
|
||||
&LocalStorage(ref mut slot) => {
|
||||
*slot = Some(vec!());
|
||||
match *slot {
|
||||
Some(ref mut map_ptr) => { return map_ptr }
|
||||
Some(ref mut map_ptr) => { return Some(map_ptr) }
|
||||
None => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -156,7 +158,10 @@ impl<T: 'static> KeyValue<T> {
|
|||
/// assert_eq!(foo.replace(None), Some(4));
|
||||
/// ```
|
||||
pub fn replace(&'static self, data: Option<T>) -> Option<T> {
|
||||
let map = unsafe { get_local_map() };
|
||||
let map = match unsafe { get_local_map() } {
|
||||
Some(map) => map,
|
||||
None => fail!("must have a local task to insert into TLD"),
|
||||
};
|
||||
let keyval = key_to_key_value(self);
|
||||
|
||||
// When the task-local map is destroyed, all the data needs to be
|
||||
|
@ -223,7 +228,10 @@ impl<T: 'static> KeyValue<T> {
|
|||
/// assert_eq!(*key.get().unwrap(), 3);
|
||||
/// ```
|
||||
pub fn get(&'static self) -> Option<Ref<T>> {
|
||||
let map = unsafe { get_local_map() };
|
||||
let map = match unsafe { get_local_map() } {
|
||||
Some(map) => map,
|
||||
None => return None,
|
||||
};
|
||||
|
||||
self.find(map).map(|(pos, data, loan)| {
|
||||
*loan += 1;
|
||||
|
@ -260,7 +268,7 @@ impl<T: 'static> Deref<T> for Ref<T> {
|
|||
#[unsafe_destructor]
|
||||
impl<T: 'static> Drop for Ref<T> {
|
||||
fn drop(&mut self) {
|
||||
let map = unsafe { get_local_map() };
|
||||
let map = unsafe { get_local_map().unwrap() };
|
||||
|
||||
let (_, _, ref mut loan) = *map.get_mut(self._index).get_mut_ref();
|
||||
*loan -= 1;
|
||||
|
|
|
@ -171,7 +171,10 @@ impl<'a> LocalIo<'a> {
|
|||
//
|
||||
// In order to get around this, we just transmute a copy out of the task
|
||||
// in order to have what is likely a static lifetime (bad).
|
||||
let mut t: Box<Task> = Local::take();
|
||||
let mut t: Box<Task> = match Local::try_take() {
|
||||
Some(t) => t,
|
||||
None => return None,
|
||||
};
|
||||
let ret = t.local_io().map(|t| {
|
||||
unsafe { mem::transmute_copy(&t) }
|
||||
});
|
||||
|
|
|
@ -66,7 +66,7 @@ use option::{Some, None, Option};
|
|||
use owned::Box;
|
||||
use prelude::drop;
|
||||
use ptr::RawPtr;
|
||||
use result::{Err, Ok};
|
||||
use result::{Err, Ok, Result};
|
||||
use rt::backtrace;
|
||||
use rt::local::Local;
|
||||
use rt::task::Task;
|
||||
|
@ -81,6 +81,11 @@ pub struct Unwinder {
|
|||
cause: Option<Box<Any:Send>>
|
||||
}
|
||||
|
||||
struct Exception {
|
||||
uwe: uw::_Unwind_Exception,
|
||||
cause: Option<Box<Any:Send>>,
|
||||
}
|
||||
|
||||
impl Unwinder {
|
||||
pub fn new() -> Unwinder {
|
||||
Unwinder {
|
||||
|
@ -94,71 +99,7 @@ impl Unwinder {
|
|||
}
|
||||
|
||||
pub fn try(&mut self, f: ||) {
|
||||
use raw::Closure;
|
||||
use libc::{c_void};
|
||||
|
||||
unsafe {
|
||||
let closure: Closure = mem::transmute(f);
|
||||
let ep = rust_try(try_fn, closure.code as *c_void,
|
||||
closure.env as *c_void);
|
||||
if !ep.is_null() {
|
||||
rtdebug!("caught {}", (*ep).exception_class);
|
||||
uw::_Unwind_DeleteException(ep);
|
||||
}
|
||||
}
|
||||
|
||||
extern fn try_fn(code: *c_void, env: *c_void) {
|
||||
unsafe {
|
||||
let closure: || = mem::transmute(Closure {
|
||||
code: code as *(),
|
||||
env: env as *(),
|
||||
});
|
||||
closure();
|
||||
}
|
||||
}
|
||||
|
||||
extern {
|
||||
// Rust's try-catch
|
||||
// When f(...) returns normally, the return value is null.
|
||||
// When f(...) throws, the return value is a pointer to the caught
|
||||
// exception object.
|
||||
fn rust_try(f: extern "C" fn(*c_void, *c_void),
|
||||
code: *c_void,
|
||||
data: *c_void) -> *uw::_Unwind_Exception;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn begin_unwind(&mut self, cause: Box<Any:Send>) -> ! {
|
||||
rtdebug!("begin_unwind()");
|
||||
|
||||
self.unwinding = true;
|
||||
self.cause = Some(cause);
|
||||
|
||||
rust_fail();
|
||||
|
||||
// An uninlined, unmangled function upon which to slap yer breakpoints
|
||||
#[inline(never)]
|
||||
#[no_mangle]
|
||||
fn rust_fail() -> ! {
|
||||
unsafe {
|
||||
let exception = box uw::_Unwind_Exception {
|
||||
exception_class: rust_exception_class(),
|
||||
exception_cleanup: exception_cleanup,
|
||||
private: [0, ..uw::unwinder_private_data_size],
|
||||
};
|
||||
let error = uw::_Unwind_RaiseException(mem::transmute(exception));
|
||||
rtabort!("Could not unwind stack, error = {}", error as int)
|
||||
}
|
||||
|
||||
extern "C" fn exception_cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
||||
exception: *uw::_Unwind_Exception) {
|
||||
rtdebug!("exception_cleanup()");
|
||||
unsafe {
|
||||
let _: Box<uw::_Unwind_Exception> =
|
||||
mem::transmute(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.cause = unsafe { try(f) }.err();
|
||||
}
|
||||
|
||||
pub fn result(&mut self) -> TaskResult {
|
||||
|
@ -170,6 +111,92 @@ impl Unwinder {
|
|||
}
|
||||
}
|
||||
|
||||
/// Invoke a closure, capturing the cause of failure if one occurs.
|
||||
///
|
||||
/// This function will return `None` if the closure did not fail, and will
|
||||
/// return `Some(cause)` if the closure fails. The `cause` returned is the
|
||||
/// object with which failure was originally invoked.
|
||||
///
|
||||
/// This function also is unsafe for a variety of reasons:
|
||||
///
|
||||
/// * This is not safe to call in a nested fashion. The unwinding
|
||||
/// interface for Rust is designed to have at most one try/catch block per
|
||||
/// task, not multiple. No runtime checking is currently performed to uphold
|
||||
/// this invariant, so this function is not safe. A nested try/catch block
|
||||
/// may result in corruption of the outer try/catch block's state, especially
|
||||
/// if this is used within a task itself.
|
||||
///
|
||||
/// * It is not sound to trigger unwinding while already unwinding. Rust tasks
|
||||
/// have runtime checks in place to ensure this invariant, but it is not
|
||||
/// guaranteed that a rust task is in place when invoking this function.
|
||||
/// Unwinding twice can lead to resource leaks where some destructors are not
|
||||
/// run.
|
||||
pub unsafe fn try(f: ||) -> Result<(), Box<Any:Send>> {
|
||||
use raw::Closure;
|
||||
use libc::{c_void};
|
||||
|
||||
let closure: Closure = mem::transmute(f);
|
||||
let ep = rust_try(try_fn, closure.code as *c_void,
|
||||
closure.env as *c_void);
|
||||
return if ep.is_null() {
|
||||
Ok(())
|
||||
} else {
|
||||
let my_ep = ep as *mut Exception;
|
||||
rtdebug!("caught {}", (*my_ep).uwe.exception_class);
|
||||
let cause = (*my_ep).cause.take();
|
||||
uw::_Unwind_DeleteException(ep);
|
||||
Err(cause.unwrap())
|
||||
};
|
||||
|
||||
extern fn try_fn(code: *c_void, env: *c_void) {
|
||||
unsafe {
|
||||
let closure: || = mem::transmute(Closure {
|
||||
code: code as *(),
|
||||
env: env as *(),
|
||||
});
|
||||
closure();
|
||||
}
|
||||
}
|
||||
|
||||
extern {
|
||||
// Rust's try-catch
|
||||
// When f(...) returns normally, the return value is null.
|
||||
// When f(...) throws, the return value is a pointer to the caught
|
||||
// exception object.
|
||||
fn rust_try(f: extern "C" fn(*c_void, *c_void),
|
||||
code: *c_void,
|
||||
data: *c_void) -> *uw::_Unwind_Exception;
|
||||
}
|
||||
}
|
||||
|
||||
// An uninlined, unmangled function upon which to slap yer breakpoints
|
||||
#[inline(never)]
|
||||
#[no_mangle]
|
||||
fn rust_fail(cause: Box<Any:Send>) -> ! {
|
||||
rtdebug!("begin_unwind()");
|
||||
|
||||
unsafe {
|
||||
let exception = box Exception {
|
||||
uwe: uw::_Unwind_Exception {
|
||||
exception_class: rust_exception_class(),
|
||||
exception_cleanup: exception_cleanup,
|
||||
private: [0, ..uw::unwinder_private_data_size],
|
||||
},
|
||||
cause: Some(cause),
|
||||
};
|
||||
let error = uw::_Unwind_RaiseException(mem::transmute(exception));
|
||||
rtabort!("Could not unwind stack, error = {}", error as int)
|
||||
}
|
||||
|
||||
extern fn exception_cleanup(_unwind_code: uw::_Unwind_Reason_Code,
|
||||
exception: *uw::_Unwind_Exception) {
|
||||
rtdebug!("exception_cleanup()");
|
||||
unsafe {
|
||||
let _: Box<Exception> = mem::transmute(exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Rust's exception class identifier. This is used by personality routines to
|
||||
// determine whether the exception was thrown by their own runtime.
|
||||
fn rust_exception_class() -> uw::_Unwind_Exception_Class {
|
||||
|
@ -346,103 +373,124 @@ pub fn begin_unwind<M: Any + Send>(msg: M, file: &'static str, line: uint) -> !
|
|||
fn begin_unwind_inner(msg: Box<Any:Send>,
|
||||
file: &'static str,
|
||||
line: uint) -> ! {
|
||||
let mut task;
|
||||
{
|
||||
let msg_s = match msg.as_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match msg.as_ref::<String>() {
|
||||
Some(s) => s.as_slice(),
|
||||
None => "Box<Any>",
|
||||
}
|
||||
};
|
||||
// First up, print the message that we're failing
|
||||
print_failure(msg, file, line);
|
||||
|
||||
// It is assumed that all reasonable rust code will have a local task at
|
||||
// all times. This means that this `try_take` will succeed almost all of
|
||||
// the time. There are border cases, however, when the runtime has
|
||||
// *almost* set up the local task, but hasn't quite gotten there yet. In
|
||||
// order to get some better diagnostics, we print on failure and
|
||||
// immediately abort the whole process if there is no local task
|
||||
// available.
|
||||
let opt_task: Option<Box<Task>> = Local::try_take();
|
||||
task = match opt_task {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
rterrln!("failed at '{}', {}:{}", msg_s, file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let mut err = ::rt::util::Stderr;
|
||||
let _err = backtrace::write(&mut err);
|
||||
} else {
|
||||
rterrln!("run with `RUST_BACKTRACE=1` to see a backtrace");
|
||||
}
|
||||
unsafe { intrinsics::abort() }
|
||||
}
|
||||
};
|
||||
|
||||
// See comments in io::stdio::with_task_stdout as to why we have to be
|
||||
// careful when using an arbitrary I/O handle from the task. We
|
||||
// essentially need to dance to make sure when a task is in TLS when
|
||||
// running user code.
|
||||
let name = task.name.take();
|
||||
{
|
||||
let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
||||
|
||||
match task.stderr.take() {
|
||||
Some(mut stderr) => {
|
||||
Local::put(task);
|
||||
// FIXME: what to do when the task printing fails?
|
||||
let _err = write!(stderr,
|
||||
"task '{}' failed at '{}', {}:{}\n",
|
||||
n, msg_s, file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let _err = backtrace::write(stderr);
|
||||
}
|
||||
task = Local::take();
|
||||
|
||||
match mem::replace(&mut task.stderr, Some(stderr)) {
|
||||
Some(prev) => {
|
||||
Local::put(task);
|
||||
drop(prev);
|
||||
task = Local::take();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rterrln!("task '{}' failed at '{}', {}:{}", n, msg_s,
|
||||
file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let mut err = ::rt::util::Stderr;
|
||||
let _err = backtrace::write(&mut err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
task.name = name;
|
||||
|
||||
if task.unwinder.unwinding {
|
||||
let opt_task: Option<Box<Task>> = Local::try_take();
|
||||
match opt_task {
|
||||
Some(mut task) => {
|
||||
// Now that we've printed why we're failing, do a check
|
||||
// to make sure that we're not double failing.
|
||||
//
|
||||
// If a task fails while it's already unwinding then we
|
||||
// have limited options. Currently our preference is to
|
||||
// just abort. In the future we may consider resuming
|
||||
// unwinding or otherwise exiting the task cleanly.
|
||||
rterrln!("task failed during unwinding (double-failure - total drag!)")
|
||||
rterrln!("rust must abort now. so sorry.");
|
||||
if task.unwinder.unwinding {
|
||||
rterrln!("task failed during unwinding (double-failure - \
|
||||
total drag!)")
|
||||
rterrln!("rust must abort now. so sorry.");
|
||||
|
||||
// Don't print the backtrace twice (it would have already been
|
||||
// printed if logging was enabled).
|
||||
if !backtrace::log_enabled() {
|
||||
// Don't print the backtrace twice (it would have already been
|
||||
// printed if logging was enabled).
|
||||
if !backtrace::log_enabled() {
|
||||
let mut err = ::rt::util::Stderr;
|
||||
let _err = backtrace::write(&mut err);
|
||||
}
|
||||
unsafe { intrinsics::abort() }
|
||||
}
|
||||
|
||||
// Finally, we've printed our failure and figured out we're not in a
|
||||
// double failure, so flag that we've started to unwind and then
|
||||
// actually unwind. Be sure that the task is in TLS so destructors
|
||||
// can do fun things like I/O.
|
||||
task.unwinder.unwinding = true;
|
||||
Local::put(task);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
rust_fail(msg)
|
||||
}
|
||||
|
||||
/// Given a failure message and the location that it occurred, prints the
|
||||
/// message to the local task's appropriate stream.
|
||||
///
|
||||
/// This function currently handles three cases:
|
||||
///
|
||||
/// - There is no local task available. In this case the error is printed to
|
||||
/// stderr.
|
||||
/// - There is a local task available, but it does not have a stderr handle.
|
||||
/// In this case the message is also printed to stderr.
|
||||
/// - There is a local task available, and it has a stderr handle. The
|
||||
/// message is printed to the handle given in this case.
|
||||
fn print_failure(msg: &Any:Send, file: &str, line: uint) {
|
||||
let msg = match msg.as_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match msg.as_ref::<String>() {
|
||||
Some(s) => s.as_slice(),
|
||||
None => "Box<Any>",
|
||||
}
|
||||
};
|
||||
|
||||
// It is assumed that all reasonable rust code will have a local task at
|
||||
// all times. This means that this `try_take` will succeed almost all of
|
||||
// the time. There are border cases, however, when the runtime has
|
||||
// *almost* set up the local task, but hasn't quite gotten there yet. In
|
||||
// order to get some better diagnostics, we print on failure and
|
||||
// immediately abort the whole process if there is no local task
|
||||
// available.
|
||||
let mut task: Box<Task> = match Local::try_take() {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
rterrln!("failed at '{}', {}:{}", msg, file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let mut err = ::rt::util::Stderr;
|
||||
let _err = backtrace::write(&mut err);
|
||||
} else {
|
||||
rterrln!("run with `RUST_BACKTRACE=1` to see a backtrace");
|
||||
}
|
||||
return
|
||||
}
|
||||
};
|
||||
|
||||
// See comments in io::stdio::with_task_stdout as to why we have to be
|
||||
// careful when using an arbitrary I/O handle from the task. We
|
||||
// essentially need to dance to make sure when a task is in TLS when
|
||||
// running user code.
|
||||
let name = task.name.take();
|
||||
{
|
||||
let n = name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
|
||||
|
||||
match task.stderr.take() {
|
||||
Some(mut stderr) => {
|
||||
Local::put(task);
|
||||
// FIXME: what to do when the task printing fails?
|
||||
let _err = write!(stderr,
|
||||
"task '{}' failed at '{}', {}:{}\n",
|
||||
n, msg, file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let _err = backtrace::write(stderr);
|
||||
}
|
||||
task = Local::take();
|
||||
|
||||
match mem::replace(&mut task.stderr, Some(stderr)) {
|
||||
Some(prev) => {
|
||||
Local::put(task);
|
||||
drop(prev);
|
||||
task = Local::take();
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
None => {
|
||||
rterrln!("task '{}' failed at '{}', {}:{}", n, msg, file, line);
|
||||
if backtrace::log_enabled() {
|
||||
let mut err = ::rt::util::Stderr;
|
||||
let _err = backtrace::write(&mut err);
|
||||
}
|
||||
}
|
||||
unsafe { intrinsics::abort() }
|
||||
}
|
||||
}
|
||||
|
||||
// The unwinder won't actually use the task at all, so we put the task back
|
||||
// into TLS right before we invoke the unwinder, but this means we need an
|
||||
// unsafe reference back to the unwinder once it's in TLS.
|
||||
task.name = name;
|
||||
Local::put(task);
|
||||
unsafe {
|
||||
let task: *mut Task = Local::unsafe_borrow();
|
||||
(*task).unwinder.begin_unwind(msg);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -176,7 +176,10 @@ impl TaskBuilder {
|
|||
Some(gen) => gen(f),
|
||||
None => f
|
||||
};
|
||||
let t: Box<Task> = Local::take();
|
||||
let t: Box<Task> = match Local::try_take() {
|
||||
Some(t) => t,
|
||||
None => fail!("need a local task to spawn a new task"),
|
||||
};
|
||||
t.spawn_sibling(self.opts, f);
|
||||
}
|
||||
|
||||
|
|
59
src/test/run-pass/running-with-no-runtime.rs
Normal file
59
src/test/run-pass/running-with-no-runtime.rs
Normal file
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
||||
// file at the top-level directory of this distribution and at
|
||||
// http://rust-lang.org/COPYRIGHT.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||
// option. This file may not be copied, modified, or distributed
|
||||
// except according to those terms.
|
||||
|
||||
extern crate native;
|
||||
|
||||
use std::io::process::{Command, ProcessOutput};
|
||||
use std::os;
|
||||
use std::str;
|
||||
use std::rt::unwind::try;
|
||||
|
||||
local_data_key!(foo: int)
|
||||
|
||||
#[start]
|
||||
fn start(argc: int, argv: **u8) -> int {
|
||||
if argc > 1 {
|
||||
unsafe {
|
||||
match **argv.offset(1) {
|
||||
1 => {}
|
||||
2 => println!("foo"),
|
||||
3 => assert!(try(|| {}).is_ok()),
|
||||
4 => assert!(try(|| fail!()).is_err()),
|
||||
5 => assert!(try(|| spawn(proc() {})).is_err()),
|
||||
6 => assert!(Command::new("test").spawn().is_err()),
|
||||
7 => assert!(foo.get().is_some()),
|
||||
8 => assert!(try(|| { foo.replace(Some(3)); }).is_err()),
|
||||
_ => fail!()
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
native::start(argc, argv, main)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let args = os::args();
|
||||
let me = args.get(0).as_slice();
|
||||
|
||||
pass(Command::new(me).arg(&[1u8]).output().unwrap());
|
||||
pass(Command::new(me).arg(&[2u8]).output().unwrap());
|
||||
pass(Command::new(me).arg(&[3u8]).output().unwrap());
|
||||
pass(Command::new(me).arg(&[4u8]).output().unwrap());
|
||||
pass(Command::new(me).arg(&[5u8]).output().unwrap());
|
||||
pass(Command::new(me).arg(&[6u8]).output().unwrap());
|
||||
}
|
||||
|
||||
fn pass(output: ProcessOutput) {
|
||||
if !output.status.success() {
|
||||
println!("{}", str::from_utf8(output.output.as_slice()));
|
||||
println!("{}", str::from_utf8(output.error.as_slice()));
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue