std: Improve non-task-based usage
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. * Invoking get() on a TLD key will return None if no task is available * Invoking replace() on a TLD key will fail if no task is available. 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:
parent
d130acc0d0
commit
0c7c93b8e8
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>;
|
type TLSValue = Box<LocalData:Send>;
|
||||||
|
|
||||||
// Gets the map from the runtime. Lazily initialises if not done so already.
|
// 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;
|
use rt::local::Local;
|
||||||
|
|
||||||
|
if !Local::exists(None::<Task>) { return None }
|
||||||
|
|
||||||
let task: *mut Task = Local::unsafe_borrow();
|
let task: *mut Task = Local::unsafe_borrow();
|
||||||
match &mut (*task).storage {
|
match &mut (*task).storage {
|
||||||
// If the at_exit function is already set, then we just need to take
|
// If the at_exit function is already set, then we just need to take
|
||||||
// a loan out on the TLS map stored inside
|
// a loan out on the TLS map stored inside
|
||||||
&LocalStorage(Some(ref mut map_ptr)) => {
|
&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
|
// If this is the first time we've accessed TLS, perform similar
|
||||||
// actions to the oldsched way of doing things.
|
// actions to the oldsched way of doing things.
|
||||||
&LocalStorage(ref mut slot) => {
|
&LocalStorage(ref mut slot) => {
|
||||||
*slot = Some(vec!());
|
*slot = Some(vec!());
|
||||||
match *slot {
|
match *slot {
|
||||||
Some(ref mut map_ptr) => { return map_ptr }
|
Some(ref mut map_ptr) => { return Some(map_ptr) }
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -156,7 +158,10 @@ impl<T: 'static> KeyValue<T> {
|
||||||
/// assert_eq!(foo.replace(None), Some(4));
|
/// assert_eq!(foo.replace(None), Some(4));
|
||||||
/// ```
|
/// ```
|
||||||
pub fn replace(&'static self, data: Option<T>) -> Option<T> {
|
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);
|
let keyval = key_to_key_value(self);
|
||||||
|
|
||||||
// When the task-local map is destroyed, all the data needs to be
|
// 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);
|
/// assert_eq!(*key.get().unwrap(), 3);
|
||||||
/// ```
|
/// ```
|
||||||
pub fn get(&'static self) -> Option<Ref<T>> {
|
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)| {
|
self.find(map).map(|(pos, data, loan)| {
|
||||||
*loan += 1;
|
*loan += 1;
|
||||||
|
@ -260,7 +268,7 @@ impl<T: 'static> Deref<T> for Ref<T> {
|
||||||
#[unsafe_destructor]
|
#[unsafe_destructor]
|
||||||
impl<T: 'static> Drop for Ref<T> {
|
impl<T: 'static> Drop for Ref<T> {
|
||||||
fn drop(&mut self) {
|
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();
|
let (_, _, ref mut loan) = *map.get_mut(self._index).get_mut_ref();
|
||||||
*loan -= 1;
|
*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 get around this, we just transmute a copy out of the task
|
||||||
// in order to have what is likely a static lifetime (bad).
|
// 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| {
|
let ret = t.local_io().map(|t| {
|
||||||
unsafe { mem::transmute_copy(&t) }
|
unsafe { mem::transmute_copy(&t) }
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,7 +66,7 @@ use option::{Some, None, Option};
|
||||||
use owned::Box;
|
use owned::Box;
|
||||||
use prelude::drop;
|
use prelude::drop;
|
||||||
use ptr::RawPtr;
|
use ptr::RawPtr;
|
||||||
use result::{Err, Ok};
|
use result::{Err, Ok, Result};
|
||||||
use rt::backtrace;
|
use rt::backtrace;
|
||||||
use rt::local::Local;
|
use rt::local::Local;
|
||||||
use rt::task::Task;
|
use rt::task::Task;
|
||||||
|
@ -81,6 +81,11 @@ pub struct Unwinder {
|
||||||
cause: Option<Box<Any:Send>>
|
cause: Option<Box<Any:Send>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Exception {
|
||||||
|
uwe: uw::_Unwind_Exception,
|
||||||
|
cause: Option<Box<Any:Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl Unwinder {
|
impl Unwinder {
|
||||||
pub fn new() -> Unwinder {
|
pub fn new() -> Unwinder {
|
||||||
Unwinder {
|
Unwinder {
|
||||||
|
@ -94,71 +99,7 @@ impl Unwinder {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try(&mut self, f: ||) {
|
pub fn try(&mut self, f: ||) {
|
||||||
use raw::Closure;
|
self.cause = unsafe { try(f) }.err();
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn result(&mut self) -> TaskResult {
|
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
|
// Rust's exception class identifier. This is used by personality routines to
|
||||||
// determine whether the exception was thrown by their own runtime.
|
// determine whether the exception was thrown by their own runtime.
|
||||||
fn rust_exception_class() -> uw::_Unwind_Exception_Class {
|
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>,
|
fn begin_unwind_inner(msg: Box<Any:Send>,
|
||||||
file: &'static str,
|
file: &'static str,
|
||||||
line: uint) -> ! {
|
line: uint) -> ! {
|
||||||
let mut task;
|
// First up, print the message that we're failing
|
||||||
{
|
print_failure(msg, file, line);
|
||||||
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>",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// It is assumed that all reasonable rust code will have a local task at
|
let opt_task: Option<Box<Task>> = Local::try_take();
|
||||||
// all times. This means that this `try_take` will succeed almost all of
|
match opt_task {
|
||||||
// the time. There are border cases, however, when the runtime has
|
Some(mut task) => {
|
||||||
// *almost* set up the local task, but hasn't quite gotten there yet. In
|
// Now that we've printed why we're failing, do a check
|
||||||
// order to get some better diagnostics, we print on failure and
|
// to make sure that we're not double failing.
|
||||||
// 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 {
|
|
||||||
// If a task fails while it's already unwinding then we
|
// If a task fails while it's already unwinding then we
|
||||||
// have limited options. Currently our preference is to
|
// have limited options. Currently our preference is to
|
||||||
// just abort. In the future we may consider resuming
|
// just abort. In the future we may consider resuming
|
||||||
// unwinding or otherwise exiting the task cleanly.
|
// unwinding or otherwise exiting the task cleanly.
|
||||||
rterrln!("task failed during unwinding (double-failure - total drag!)")
|
if task.unwinder.unwinding {
|
||||||
rterrln!("rust must abort now. so sorry.");
|
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
|
// Don't print the backtrace twice (it would have already been
|
||||||
// printed if logging was enabled).
|
// printed if logging was enabled).
|
||||||
if !backtrace::log_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 mut err = ::rt::util::Stderr;
|
||||||
let _err = backtrace::write(&mut err);
|
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() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
task.name = name;
|
||||||
// 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.
|
|
||||||
Local::put(task);
|
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),
|
Some(gen) => gen(f),
|
||||||
None => 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);
|
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…
Add table
Add a link
Reference in a new issue