diff --git a/src/libstd/rt/kill.rs b/src/libstd/rt/kill.rs index e691bf51ea50e..c2571f171a172 100644 --- a/src/libstd/rt/kill.rs +++ b/src/libstd/rt/kill.rs @@ -84,7 +84,7 @@ pub struct Death { on_exit: Option<~fn(bool)>, // nesting level counter for task::unkillable calls (0 == killable). unkillable: int, - // nesting level counter for task::atomically calls (0 == can yield). + // nesting level counter for unstable::atomically calls (0 == can yield). wont_sleep: int, // A "spare" handle to the kill flag inside the kill handle. Used during // blocking/waking as an optimization to avoid two xadds on the refcount. diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index dc8669b9264cc..c38b929a6cea0 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -316,12 +316,14 @@ fn run_(main: ~fn(), use_main_sched: bool) -> int { // Just put an unpinned task onto one of the default schedulers. let mut main_task = ~Task::new_root(&mut scheds[0].stack_pool, main); main_task.death.on_exit = Some(on_exit); + main_task.name = Some(~"main"); scheds[0].enqueue_task(main_task); } Some(ref mut main_sched) => { let home = Sched(main_sched.make_handle()); let mut main_task = ~Task::new_root_homed(&mut scheds[0].stack_pool, home, main); main_task.death.on_exit = Some(on_exit); + main_task.name = Some(~"main"); main_sched.enqueue_task(main_task); } }; diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index 82d4f8fcc0424..c1b799796d158 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -40,7 +40,9 @@ pub struct Task { taskgroup: Option, death: Death, destroyed: bool, - coroutine: Option<~Coroutine> + coroutine: Option<~Coroutine>, + // FIXME(#6874/#7599) use StringRef to save on allocations + name: Option<~str>, } pub struct Coroutine { @@ -90,7 +92,8 @@ impl Task { taskgroup: None, death: Death::new(), destroyed: false, - coroutine: Some(~Coroutine::new(stack_pool, start)) + coroutine: Some(~Coroutine::new(stack_pool, start)), + name: None, } } @@ -109,7 +112,8 @@ impl Task { // FIXME(#7544) make watching optional death: self.death.new_child(), destroyed: false, - coroutine: Some(~Coroutine::new(stack_pool, start)) + coroutine: Some(~Coroutine::new(stack_pool, start)), + name: None, } } diff --git a/src/libstd/sys.rs b/src/libstd/sys.rs index 5cf77d901db4c..4ca5d82265fbf 100644 --- a/src/libstd/sys.rs +++ b/src/libstd/sys.rs @@ -137,12 +137,13 @@ impl FailWithCause for &'static str { // FIXME #4427: Temporary until rt::rt_fail_ goes away pub fn begin_unwind_(msg: *c_char, file: *c_char, line: size_t) -> ! { - use cell::Cell; use either::Left; + use option::{Some, None}; use rt::{context, OldTaskContext, TaskContext}; use rt::task::Task; use rt::local::Local; use rt::logging::Logger; + use str::Str; let context = context(); match context { @@ -159,20 +160,26 @@ pub fn begin_unwind_(msg: *c_char, file: *c_char, line: size_t) -> ! { let msg = str::raw::from_c_str(msg); let file = str::raw::from_c_str(file); - let outmsg = fmt!("task failed at '%s', %s:%i", - msg, file, line as int); - // XXX: Logging doesn't work correctly in non-task context because it // invokes the local heap if context == TaskContext { // XXX: Logging doesn't work here - the check to call the log // function never passes - so calling the log function directly. - let outmsg = Cell::new(outmsg); do Local::borrow:: |task| { - task.logger.log(Left(outmsg.take())); + let msg = match task.name { + Some(ref name) => + fmt!("task '%s' failed at '%s', %s:%i", + name.as_slice(), msg, file, line as int), + None => + fmt!("task failed at '%s', %s:%i", + msg, file, line as int) + }; + + task.logger.log(Left(msg)); } } else { - rterrln!("%s", outmsg); + rterrln!("failed in non-task context at '%s', %s:%i", + msg, file, line as int); } gc::cleanup_stack_for_failure(); diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs index 6de0c78d00b51..aff4bc12039fc 100644 --- a/src/libstd/task/mod.rs +++ b/src/libstd/task/mod.rs @@ -120,6 +120,8 @@ pub struct SchedOpts { * * * notify_chan - Enable lifecycle notifications on the given channel * + * * name - A name for the task-to-be, for identification in failure messages. + * * * sched - Specify the configuration of a new scheduler to create the task * in * @@ -139,6 +141,7 @@ pub struct TaskOpts { watched: bool, indestructible: bool, notify_chan: Option>, + name: Option<~str>, sched: SchedOpts } @@ -185,6 +188,7 @@ impl TaskBuilder { self.consumed = true; let gen_body = self.gen_body.take(); let notify_chan = self.opts.notify_chan.take(); + let name = self.opts.name.take(); TaskBuilder { opts: TaskOpts { linked: self.opts.linked, @@ -192,6 +196,7 @@ impl TaskBuilder { watched: self.opts.watched, indestructible: self.opts.indestructible, notify_chan: notify_chan, + name: name, sched: self.opts.sched }, gen_body: gen_body, @@ -199,9 +204,7 @@ impl TaskBuilder { consumed: false } } -} -impl TaskBuilder { /// Decouple the child task's failure from the parent's. If either fails, /// the other will not be killed. pub fn unlinked(&mut self) { @@ -281,6 +284,12 @@ impl TaskBuilder { self.opts.notify_chan = Some(notify_pipe_ch); } + /// Name the task-to-be. Currently the name is used for identification + /// only in failure messages. + pub fn name(&mut self, name: ~str) { + self.opts.name = Some(name); + } + /// Configure a custom scheduler mode for the task. pub fn sched_mode(&mut self, mode: SchedMode) { self.opts.sched.mode = mode; @@ -333,6 +342,7 @@ impl TaskBuilder { pub fn spawn(&mut self, f: ~fn()) { let gen_body = self.gen_body.take(); let notify_chan = self.opts.notify_chan.take(); + let name = self.opts.name.take(); let x = self.consume(); let opts = TaskOpts { linked: x.opts.linked, @@ -340,6 +350,7 @@ impl TaskBuilder { watched: x.opts.watched, indestructible: x.opts.indestructible, notify_chan: notify_chan, + name: name, sched: x.opts.sched }; let f = match gen_body { @@ -408,6 +419,7 @@ pub fn default_task_opts() -> TaskOpts { watched: true, indestructible: false, notify_chan: None, + name: None, sched: SchedOpts { mode: DefaultScheduler, } @@ -507,6 +519,21 @@ pub fn try(f: ~fn() -> T) -> Result { /* Lifecycle functions */ +/// Read the name of the current task. +pub fn with_task_name(blk: &fn(Option<&str>) -> U) -> U { + use rt::task::Task; + + match context() { + TaskContext => do Local::borrow:: |task| { + match task.name { + Some(ref name) => blk(Some(name.as_slice())), + None => blk(None) + } + }, + _ => fail!("no task name exists in %?", context()), + } +} + pub fn yield() { //! Yield control to the task scheduler @@ -628,44 +655,6 @@ pub unsafe fn rekillable(f: &fn() -> U) -> U { } } -/** - * A stronger version of unkillable that also inhibits scheduling operations. - * For use with exclusive Arcs, which use pthread mutexes directly. - */ -pub unsafe fn atomically(f: &fn() -> U) -> U { - use rt::task::Task; - - match context() { - OldTaskContext => { - let t = rt::rust_get_task(); - do (|| { - rt::rust_task_inhibit_kill(t); - rt::rust_task_inhibit_yield(t); - f() - }).finally { - rt::rust_task_allow_yield(t); - rt::rust_task_allow_kill(t); - } - } - TaskContext => { - let t = Local::unsafe_borrow::(); - do (|| { - // It's important to inhibit kill after inhibiting yield, because - // inhibit-kill might fail if we were already killed, and the - // inhibit-yield must happen to match the finally's allow-yield. - (*t).death.inhibit_yield(); - (*t).death.inhibit_kill((*t).unwinder.unwinding); - f() - }).finally { - (*t).death.allow_kill((*t).unwinder.unwinding); - (*t).death.allow_yield(); - } - } - // FIXME(#3095): As in unkillable(). - _ => f() - } -} - #[test] #[should_fail] #[ignore(cfg(windows))] fn test_cant_dup_task_builder() { let mut builder = task(); @@ -805,6 +794,34 @@ fn test_spawn_linked_sup_propagate_sibling() { fail!(); } +#[test] +fn test_unnamed_task() { + use rt::test::run_in_newsched_task; + + do run_in_newsched_task { + do spawn { + do with_task_name |name| { + assert!(name.is_none()); + } + } + } +} + +#[test] +fn test_named_task() { + use rt::test::run_in_newsched_task; + + do run_in_newsched_task { + let mut t = task(); + t.name(~"ada lovelace"); + do t.spawn { + do with_task_name |name| { + assert!(name.get() == "ada lovelace"); + } + } + } +} + #[test] fn test_run_basic() { let (po, ch) = stream::<()>(); @@ -1122,21 +1139,6 @@ fn test_unkillable_nested() { po.recv(); } -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_atomically() { - unsafe { do atomically { yield(); } } -} - -#[test] -fn test_atomically2() { - unsafe { do atomically { } } yield(); // shouldn't fail -} - -#[test] #[should_fail] #[ignore(cfg(windows))] -fn test_atomically_nested() { - unsafe { do atomically { do atomically { } yield(); } } -} - #[test] fn test_child_doesnt_ref_parent() { // If the child refcounts the parent task, this will stack overflow when diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs index 54e468269766e..7a42cc5c8b5d8 100644 --- a/src/libstd/task/spawn.rs +++ b/src/libstd/task/spawn.rs @@ -726,6 +726,8 @@ fn spawn_raw_newsched(mut opts: TaskOpts, f: ~fn()) { task.death.on_exit = Some(on_exit); } + task.name = opts.name.take(); + rtdebug!("spawn about to take scheduler"); let sched = Local::take::(); diff --git a/src/libstd/unstable/dynamic_lib.rs b/src/libstd/unstable/dynamic_lib.rs index e01f99adc94ae..ab44520454d62 100644 --- a/src/libstd/unstable/dynamic_lib.rs +++ b/src/libstd/unstable/dynamic_lib.rs @@ -105,7 +105,7 @@ mod dl { use path; use ptr; use str; - use task; + use unstable::sync::atomically; use result::*; pub unsafe fn open_external(filename: &path::Path) -> *libc::c_void { @@ -120,7 +120,7 @@ mod dl { pub fn check_for_errors_in(f: &fn()->T) -> Result { unsafe { - do task::atomically { + do atomically { let _old_error = dlerror(); let result = f(); @@ -164,7 +164,7 @@ mod dl { use libc; use path; use ptr; - use task; + use unstable::sync::atomically; use result::*; pub unsafe fn open_external(filename: &path::Path) -> *libc::c_void { @@ -181,7 +181,7 @@ mod dl { pub fn check_for_errors_in(f: &fn()->T) -> Result { unsafe { - do task::atomically { + do atomically { SetLastError(0); let result = f(); diff --git a/src/libstd/unstable/mod.rs b/src/libstd/unstable/mod.rs index 313567d1248ec..f721dd47a66a0 100644 --- a/src/libstd/unstable/mod.rs +++ b/src/libstd/unstable/mod.rs @@ -85,7 +85,7 @@ fn test_run_in_bare_thread_exchange() { pub fn change_dir_locked(p: &Path, action: &fn()) -> bool { use os; use os::change_dir; - use task; + use unstable::sync::atomically; use unstable::finally::Finally; unsafe { @@ -93,7 +93,7 @@ pub fn change_dir_locked(p: &Path, action: &fn()) -> bool { // in the `action` callback can cause deadlock. Doing it in // `task::atomically` to try to avoid that, but ... I don't know // this is all bogus. - return do task::atomically { + return do atomically { rust_take_change_dir_lock(); do (||{ diff --git a/src/libstd/unstable/sync.rs b/src/libstd/unstable/sync.rs index 4c52d897a7212..c52e4739edd9f 100644 --- a/src/libstd/unstable/sync.rs +++ b/src/libstd/unstable/sync.rs @@ -16,7 +16,6 @@ use ptr; use option::*; use either::{Either, Left, Right}; use task; -use task::atomically; use unstable::atomics::{AtomicOption,AtomicUint,Acquire,Release,SeqCst}; use unstable::finally::Finally; use ops::Drop; @@ -271,6 +270,48 @@ impl Drop for UnsafeAtomicRcBox{ /****************************************************************************/ +/** + * Enables a runtime assertion that no operation in the argument closure shall + * use scheduler operations (yield, recv, spawn, etc). This is for use with + * pthread mutexes, which may block the entire scheduler thread, rather than + * just one task, and is hence prone to deadlocks if mixed with yielding. + * + * NOTE: THIS DOES NOT PROVIDE LOCKING, or any sort of critical-section + * synchronization whatsoever. It only makes sense to use for CPU-local issues. + */ +// FIXME(#8140) should not be pub +pub unsafe fn atomically(f: &fn() -> U) -> U { + use rt::task::Task; + use task::rt; + use rt::local::Local; + use rt::{context, OldTaskContext, TaskContext}; + + match context() { + OldTaskContext => { + let t = rt::rust_get_task(); + do (|| { + rt::rust_task_inhibit_kill(t); + rt::rust_task_inhibit_yield(t); + f() + }).finally { + rt::rust_task_allow_yield(t); + rt::rust_task_allow_kill(t); + } + } + TaskContext => { + let t = Local::unsafe_borrow::(); + do (|| { + (*t).death.inhibit_yield(); + f() + }).finally { + (*t).death.allow_yield(); + } + } + // FIXME(#3095): As in unkillable(). + _ => f() + } +} + #[allow(non_camel_case_types)] // runtime type type rust_little_lock = *libc::c_void; @@ -395,11 +436,18 @@ mod tests { use cell::Cell; use comm; use option::*; - use super::{Exclusive, UnsafeAtomicRcBox}; + use super::{Exclusive, UnsafeAtomicRcBox, atomically}; use task; use uint; use util; + #[test] + fn test_atomically() { + // NB. The whole runtime will abort on an 'atomic-sleep' violation, + // so we can't really test for the converse behaviour. + unsafe { do atomically { } } task::yield(); // oughtn't fail + } + #[test] fn exclusive_new_arc() { unsafe {