diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2014-06-04 10:54:35 -0700 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2014-06-04 11:13:12 -0700 |
| commit | 0c7c93b8e83544abc7eef5abd76526e5c49882f5 (patch) | |
| tree | 79768d11e65366724ca547ddd5c0bb8598303d4a /src/libstd/rt | |
| parent | d130acc0d0e83bd0b4b94cda9d39dcbb67312526 (diff) | |
| download | rust-0c7c93b8e83544abc7eef5abd76526e5c49882f5.tar.gz rust-0c7c93b8e83544abc7eef5abd76526e5c49882f5.zip | |
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.
Diffstat (limited to 'src/libstd/rt')
| -rw-r--r-- | src/libstd/rt/rtio.rs | 5 | ||||
| -rw-r--r-- | src/libstd/rt/unwind.rs | 344 |
2 files changed, 200 insertions, 149 deletions
diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs index a6c60df2642..06db465f7ee 100644 --- a/src/libstd/rt/rtio.rs +++ b/src/libstd/rt/rtio.rs @@ -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) } }); diff --git a/src/libstd/rt/unwind.rs b/src/libstd/rt/unwind.rs index dc2646102d2..c455648992e 100644 --- a/src/libstd/rt/unwind.rs +++ b/src/libstd/rt/unwind.rs @@ -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,78 +99,100 @@ 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); - } - } + self.cause = unsafe { try(f) }.err(); + } - extern fn try_fn(code: *c_void, env: *c_void) { - unsafe { - let closure: || = mem::transmute(Closure { - code: code as *(), - env: env as *(), - }); - closure(); - } + pub fn result(&mut self) -> TaskResult { + if self.unwinding { + Err(self.cause.take().unwrap()) + } else { + Ok(()) } + } +} - 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; +/// 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(); } } - pub fn begin_unwind(&mut self, cause: Box<Any:Send>) -> ! { - rtdebug!("begin_unwind()"); - - self.unwinding = true; - self.cause = Some(cause); - - rust_fail(); + 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() -> ! { - 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) - } +// An uninlined, unmangled function upon which to slap yer breakpoints +#[inline(never)] +#[no_mangle] +fn rust_fail(cause: Box<Any:Send>) -> ! { + rtdebug!("begin_unwind()"); - 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); - } - } - } + 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) } - pub fn result(&mut self) -> TaskResult { - if self.unwinding { - Err(self.cause.take().unwrap()) - } else { - Ok(()) + extern fn exception_cleanup(_unwind_code: uw::_Unwind_Reason_Code, + exception: *uw::_Unwind_Exception) { + rtdebug!("exception_cleanup()"); + unsafe { + let _: Box<Exception> = mem::transmute(exception); } } } @@ -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>", - } - }; - - // 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() { + // First up, print the message that we're failing + print_failure(msg, file, line); + + 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. + 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() { 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); - } - } - } + // 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); } - task.name = name; - - if task.unwinder.unwinding { - // 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."); + None => {} + } + rust_fail(msg) +} - // Don't print the backtrace twice (it would have already been - // printed if logging was enabled). - if !backtrace::log_enabled() { +/// 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"); } - unsafe { intrinsics::abort() } + return } - } + }; - // 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); - unsafe { - let task: *mut Task = Local::unsafe_borrow(); - (*task).unwinder.begin_unwind(msg); + // 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); + } + } + } } + task.name = name; + Local::put(task); } |
