about summary refs log tree commit diff
path: root/src/libstd/rt/task.rs
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2013-10-28 01:26:16 -0700
committerbors <bors@rust-lang.org>2013-10-28 01:26:16 -0700
commit9ef23e9060d60189894f6d725e6c1c73b887c802 (patch)
tree0b8051814dd8a5ef08e663c172e2b456065d625d /src/libstd/rt/task.rs
parentcb5b21eba713ff3888b2741db4c9e7d841cfde02 (diff)
parentfa8e71a8257f4226ab532d4bf268d3ecbfa98eb4 (diff)
downloadrust-9ef23e9060d60189894f6d725e6c1c73b887c802.tar.gz
rust-9ef23e9060d60189894f6d725e6c1c73b887c802.zip
auto merge of #9967 : Kimundi/rust/fail_cause_two, r=huonw
# Summary

This PR allows the cause of a failure to be received in a task's future result and as the `Err` case of `task::try`, and also implements dynamic typing in form of an `Any` trait.

# Task failure

- `fail!` and related macros now accept 5 kinds of types: `~str`, `&'static str`, `std::send_str::SendStr`, `~std::any::Any` and `~T` where `T: Any + Send + 'static`
- `std::task::TaskResult` got transformed into an internal enum `std::rt::task::UnwindResult`,  and it's `Failure` variant now contains a value of enum type `UnwindReason`:
  - `UnwindReasonStr(SendStr)` maps to failing with a value of type `~str`, `&'static str` or `SendStr`.
  - `UnwindReasonAny(~Any)` maps to failing with an `~Any` or `~T` with `T: Send + 'static`.
  - `UnwindReasonLinked` maps to failing because the task got killed by linked failure.
- Instead, `std::task::TaskResult` is now a typedef for `Result<(), ~Any>`, and both `TaskBuilder`'s `future_result()` and `task::try` now work with a value of this type.
- `future_result()` no longer returns a `Port<TaskResult>`, instead it returns a wrapper `TaskResultPort` that implements `GenericPort` and `Peekable`, and which lazily allocates a `~` box and casts to `~Any` in case of failure with a `SendStr` or linked failure (for the latter case a unit struct `LinkedFailure` got added.)
- Because `fail!` collapses both `~str` and `&'static str` into a `SendStr`, checking if a task error value is a string will just require a `.is::<SendStr>()` check, with `~str` and `&'static str` only being possible in case of an explicit `fail!(~~"...")` or `fail!(~ (&"...") as ~Any)`:

# Any

- In order to allow failing with arbitrary data, the `Any` trait got implemented.
- It is being used in form of a trait object, usually `~Any` or `&Any`.
- `&Any`, `~Any` and `&mut Any` have a few extension methods implemented on them:
  - `is<T>(self) -> bool` returns true if the `Any` object contains type `T`
  - `as_ref<T>(self) -> Option<&T>` returns a reference to the contained type, if it is `T`
  - `as_mut<T>(self) -> Option<&mut T>` returns a mutable reference to the contained type, if it is `T`
  - `move<T>(self) -> Option<~T>` allows to get the `~T` out of an `~Any` again.
- `Any` currently uses type descriptors as a type id for comparisons, which is 
    - not reliable, as it is not guaranteed that every type has only one type descriptor.
    - But safe, as no two types share the same type descriptor.
- The implementation also a few `transmute`s, mostly to cast a `*Void` of the wrapped type into it's actual type `&T`, `&mut T` or `~T`.

# Other changes

- `std::unstable::UnsafeArc::try_unwrap` no longer uses `Either`, bringing us one step closer to removing that type.
- A few of the touched modules got their import lines sorted.
- A few stylistic cleanups here and there.

Diffstat (limited to 'src/libstd/rt/task.rs')
-rw-r--r--src/libstd/rt/task.rs174
1 files changed, 124 insertions, 50 deletions
diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs
index f82eb929a39..8f695763a25 100644
--- a/src/libstd/rt/task.rs
+++ b/src/libstd/rt/task.rs
@@ -13,29 +13,31 @@
 //! local storage, and logging. Even a 'freestanding' Rust would likely want
 //! to implement this.
 
+use super::local_heap::LocalHeap;
+
+use prelude::*;
+
 use borrow;
 use cast::transmute;
+use cell::Cell;
 use cleanup;
-use local_data;
 use libc::{c_void, uintptr_t, c_char, size_t};
-use prelude::*;
+use local_data;
 use option::{Option, Some, None};
-use rt::borrowck;
 use rt::borrowck::BorrowRecord;
+use rt::borrowck;
+use rt::context::Context;
+use rt::context;
 use rt::env;
 use rt::io::Writer;
 use rt::kill::Death;
 use rt::local::Local;
 use rt::logging::StdErrLogger;
-use super::local_heap::LocalHeap;
 use rt::sched::{Scheduler, SchedHandle};
 use rt::stack::{StackSegment, StackPool};
-use rt::context;
-use rt::context::Context;
-use unstable::finally::Finally;
-use task::spawn::Taskgroup;
-use cell::Cell;
 use send_str::SendStr;
+use task::spawn::Taskgroup;
+use unstable::finally::Finally;
 
 // The Task struct represents all state associated with a rust
 // task. There are at this point two primary "subtypes" of task,
@@ -85,8 +87,61 @@ pub enum SchedHome {
 pub struct GarbageCollector;
 pub struct LocalStorage(Option<local_data::Map>);
 
+/// Represents the reason for the current unwinding process
+pub enum UnwindResult {
+    /// The task is ending successfully
+    Success,
+
+    /// The Task is failing with reason `UnwindReason`
+    Failure(UnwindReason),
+}
+
+impl UnwindResult {
+    /// Returns `true` if this `UnwindResult` is a failure
+    #[inline]
+    pub fn is_failure(&self) -> bool {
+        match *self {
+            Success => false,
+            Failure(_) => true
+        }
+    }
+
+    /// Returns `true` if this `UnwindResult` is a success
+    #[inline]
+    pub fn is_success(&self) -> bool {
+        match *self {
+            Success => true,
+            Failure(_) => false
+        }
+    }
+}
+
+/// Represents the cause of a task failure
+#[deriving(ToStr)]
+pub enum UnwindReason {
+    /// Failed with a string message
+    UnwindReasonStr(SendStr),
+
+    /// Failed with an `~Any`
+    UnwindReasonAny(~Any),
+
+    /// Failed because of linked failure
+    UnwindReasonLinked
+}
+
 pub struct Unwinder {
     unwinding: bool,
+    cause: Option<UnwindReason>
+}
+
+impl Unwinder {
+    fn to_unwind_result(&mut self) -> UnwindResult {
+        if self.unwinding {
+            Failure(self.cause.take().unwrap())
+        } else {
+            Success
+        }
+    }
 }
 
 impl Task {
@@ -135,7 +190,7 @@ impl Task {
             gc: GarbageCollector,
             storage: LocalStorage(None),
             logger: StdErrLogger::new(),
-            unwinder: Unwinder { unwinding: false },
+            unwinder: Unwinder { unwinding: false, cause: None },
             taskgroup: None,
             death: Death::new(),
             destroyed: false,
@@ -170,7 +225,7 @@ impl Task {
             gc: GarbageCollector,
             storage: LocalStorage(None),
             logger: StdErrLogger::new(),
-            unwinder: Unwinder { unwinding: false },
+            unwinder: Unwinder { unwinding: false, cause: None },
             taskgroup: None,
             death: Death::new(),
             destroyed: false,
@@ -193,7 +248,7 @@ impl Task {
             gc: GarbageCollector,
             storage: LocalStorage(None),
             logger: StdErrLogger::new(),
-            unwinder: Unwinder { unwinding: false },
+            unwinder: Unwinder { unwinding: false, cause: None },
             taskgroup: None,
             // FIXME(#7544) make watching optional
             death: self.death.new_child(),
@@ -284,7 +339,7 @@ impl Task {
         // the unkillable counter is set. This is necessary for when the
         // taskgroup destruction code drops references on KillHandles, which
         // might require using unkillable (to synchronize with an unwrapper).
-        self.death.collect_failure(!self.unwinder.unwinding, self.taskgroup.take());
+        self.death.collect_failure(self.unwinder.to_unwind_result(), self.taskgroup.take());
         self.destroyed = true;
     }
 
@@ -469,10 +524,11 @@ impl Unwinder {
         }
     }
 
-    pub fn begin_unwind(&mut self) -> ! {
+    pub fn begin_unwind(&mut self, cause: UnwindReason) -> ! {
         #[fixed_stack_segment]; #[inline(never)];
 
         self.unwinding = true;
+        self.cause = Some(cause);
         unsafe {
             rust_begin_unwind(UNWIND_TOKEN);
             return transmute(());
@@ -561,55 +617,73 @@ pub extern "C" fn rust_stack_exhausted() {
 }
 
 /// This is the entry point of unwinding for things like lang items and such.
-/// The arguments are normally generated by the compiler.
+/// The arguments are normally generated by the compiler, and need to
+/// have static lifetimes.
 pub fn begin_unwind(msg: *c_char, file: *c_char, line: size_t) -> ! {
+    use c_str::CString;
+    use cast::transmute;
+
+    #[inline]
+    fn static_char_ptr(p: *c_char) -> &'static str {
+        let s = unsafe { CString::new(p, false) };
+        match s.as_str() {
+            Some(s) => unsafe { transmute::<&str, &'static str>(s) },
+            None => rtabort!("message wasn't utf8?")
+        }
+    }
+
+    let msg = static_char_ptr(msg);
+    let file = static_char_ptr(file);
+
+    begin_unwind_reason(UnwindReasonStr(msg.into_send_str()), file, line as uint)
+}
+
+/// This is the entry point of unwinding for fail!() and assert!().
+pub fn begin_unwind_reason(reason: UnwindReason, file: &'static str, line: uint) -> ! {
     use rt::in_green_task_context;
     use rt::task::Task;
     use rt::local::Local;
     use str::Str;
-    use c_str::CString;
     use unstable::intrinsics;
 
     unsafe {
-        let msg = CString::new(msg, false);
-        let file = CString::new(file, false);
-        let msg = match msg.as_str() {
-            Some(s) => s, None => rtabort!("message wasn't utf8?")
-        };
+        // Be careful not to allocate in this block, if we're failing we may
+        // have been failing due to a lack of memory in the first place...
 
-        if !in_green_task_context() {
-            match file.as_str() {
-                Some(file) => {
-                    rterrln!("failed in non-task context at '{}', {}:{}",
-                             msg, file, line as int);
-                }
-                None => rterrln!("failed in non-task context at '{}'", msg)
+        let task: *mut Task;
+
+        {
+            let msg = match reason {
+                UnwindReasonStr(ref s) => s.as_slice(),
+                UnwindReasonAny(_)     => "~Any",
+                UnwindReasonLinked     => "linked failure",
+            };
+
+            if !in_green_task_context() {
+                rterrln!("failed in non-task context at '{}', {}:{}",
+                        msg, file, line);
+                intrinsics::abort();
             }
-            intrinsics::abort();
-        }
 
-        // Be careful not to allocate in this block, if we're failing we may
-        // have been failing due to a lack of memory in the first place...
-        let task: *mut Task = Local::unsafe_borrow();
-        let n = (*task).name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
-
-        // XXX: this should no get forcibly printed to the console, this should
-        //      either be sent to the parent task (ideally), or get printed to
-        //      the task's logger. Right now the logger is actually a uvio
-        //      instance, which uses unkillable blocks internally for various
-        //      reasons. This will cause serious trouble if the task is failing
-        //      due to mismanagment of its own kill flag, so calling our own
-        //      logger in its current state is a bit of a problem.
-        match file.as_str() {
-            Some(file) => {
-                rterrln!("task '{}' failed at '{}', {}:{}", n, msg, file, line);
+            task = Local::unsafe_borrow();
+            let n = (*task).name.as_ref().map(|n| n.as_slice()).unwrap_or("<unnamed>");
+
+            // XXX: this should no get forcibly printed to the console, this should
+            //      either be sent to the parent task (ideally), or get printed to
+            //      the task's logger. Right now the logger is actually a uvio
+            //      instance, which uses unkillable blocks internally for various
+            //      reasons. This will cause serious trouble if the task is failing
+            //      due to mismanagment of its own kill flag, so calling our own
+            //      logger in its current state is a bit of a problem.
+
+            rterrln!("task '{}' failed at '{}', {}:{}", n, msg, file, line);
+
+            if (*task).unwinder.unwinding {
+                rtabort!("unwinding again");
             }
-            None => rterrln!("task '{}' failed at '{}'", n, msg),
         }
-        if (*task).unwinder.unwinding {
-            rtabort!("unwinding again");
-        }
-        (*task).unwinder.begin_unwind();
+
+        (*task).unwinder.begin_unwind(reason);
     }
 }