]> git.lizzy.rs Git - rust.git/commitdiff
std: Improve non-task-based usage
authorAlex Crichton <alex@alexcrichton.com>
Wed, 4 Jun 2014 17:54:35 +0000 (10:54 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Wed, 4 Jun 2014 18:13:12 +0000 (11:13 -0700)
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.

src/libstd/local_data.rs
src/libstd/rt/rtio.rs
src/libstd/rt/unwind.rs
src/libstd/task.rs
src/test/run-pass/running-with-no-runtime.rs [new file with mode: 0644]

index 7bf0b19407fe34ca5e7af0c56d019204e8da9aef..930d1df02f1269a146ce7e19e7aaa034cef86083 100644 (file)
@@ -96,22 +96,24 @@ impl<T: 'static> LocalData for T {}
 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 @@ pub fn replace(&'static self, data: Option<T>) -> Option<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 @@ fn deref<'a>(&'a self) -> &'a T { self._ptr }
 #[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;
index a6c60df2642362798c899343eddcfa535c5bc8f2..06db465f7eeedf49f8f3b7db8cd9d6000a237fe0 100644 (file)
@@ -171,7 +171,10 @@ pub fn borrow() -> Option<LocalIo> {
         //
         // 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) }
         });
index dc2646102d208ea6111c2751f115c6857dcc3321..c455648992ed85bf84f32c7fb907a870fcf36ff0 100644 (file)
@@ -66,7 +66,7 @@
 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 @@ pub fn unwinding(&self) -> bool {
     }
 
     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);
 }
index 4824a9561078fe2d234181ab906b498c7aabe88f..3b573b875744b635e1bcaf1895a42d35dc926da8 100644 (file)
@@ -176,7 +176,10 @@ pub fn spawn(mut self, f: proc():Send) {
             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);
     }
 
diff --git a/src/test/run-pass/running-with-no-runtime.rs b/src/test/run-pass/running-with-no-runtime.rs
new file mode 100644 (file)
index 0000000..1fe9933
--- /dev/null
@@ -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()));
+    }
+}