From 29d83002a27e6f47759b4a3bfe741fb061107816 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 20 May 2013 16:43:31 -0700 Subject: [PATCH 01/55] core::rt: Move uv idle tests to idle mod --- src/libcore/rt/uv/idle.rs | 62 +++++++++++++++++++++++++++++++++++++++ src/libcore/rt/uv/mod.rs | 54 ---------------------------------- 2 files changed, 62 insertions(+), 54 deletions(-) diff --git a/src/libcore/rt/uv/idle.rs b/src/libcore/rt/uv/idle.rs index 2cf0b5c48728..a81ab48696a3 100644 --- a/src/libcore/rt/uv/idle.rs +++ b/src/libcore/rt/uv/idle.rs @@ -89,3 +89,65 @@ impl NativeHandle<*uvll::uv_idle_t> for IdleWatcher { match self { &IdleWatcher(ptr) => ptr } } } + +#[cfg(test)] +mod test { + + use rt::uv::Loop; + use super::*; + use unstable::run_in_bare_thread; + + #[test] + #[ignore(reason = "valgrind - loop destroyed before watcher?")] + fn idle_new_then_close() { + do run_in_bare_thread { + let mut loop_ = Loop::new(); + let idle_watcher = { IdleWatcher::new(&mut loop_) }; + idle_watcher.close(||()); + } + } + + #[test] + fn idle_smoke_test() { + do run_in_bare_thread { + let mut loop_ = Loop::new(); + let mut idle_watcher = { IdleWatcher::new(&mut loop_) }; + let mut count = 10; + let count_ptr: *mut int = &mut count; + do idle_watcher.start |idle_watcher, status| { + let mut idle_watcher = idle_watcher; + assert!(status.is_none()); + if unsafe { *count_ptr == 10 } { + idle_watcher.stop(); + idle_watcher.close(||()); + } else { + unsafe { *count_ptr = *count_ptr + 1; } + } + } + loop_.run(); + loop_.close(); + assert_eq!(count, 10); + } + } + + #[test] + fn idle_start_stop_start() { + do run_in_bare_thread { + let mut loop_ = Loop::new(); + let mut idle_watcher = { IdleWatcher::new(&mut loop_) }; + do idle_watcher.start |idle_watcher, status| { + let mut idle_watcher = idle_watcher; + assert!(status.is_none()); + idle_watcher.stop(); + do idle_watcher.start |idle_watcher, status| { + assert!(status.is_none()); + let mut idle_watcher = idle_watcher; + idle_watcher.stop(); + idle_watcher.close(||()); + } + } + loop_.run(); + loop_.close(); + } + } +} diff --git a/src/libcore/rt/uv/mod.rs b/src/libcore/rt/uv/mod.rs index 2bd657fd8641..8cc596b2876d 100644 --- a/src/libcore/rt/uv/mod.rs +++ b/src/libcore/rt/uv/mod.rs @@ -364,57 +364,3 @@ fn loop_smoke_test() { loop_.close(); } } - -#[test] -#[ignore(reason = "valgrind - loop destroyed before watcher?")] -fn idle_new_then_close() { - do run_in_bare_thread { - let mut loop_ = Loop::new(); - let idle_watcher = { IdleWatcher::new(&mut loop_) }; - idle_watcher.close(||()); - } -} - -#[test] -fn idle_smoke_test() { - do run_in_bare_thread { - let mut loop_ = Loop::new(); - let mut idle_watcher = { IdleWatcher::new(&mut loop_) }; - let mut count = 10; - let count_ptr: *mut int = &mut count; - do idle_watcher.start |idle_watcher, status| { - let mut idle_watcher = idle_watcher; - assert!(status.is_none()); - if unsafe { *count_ptr == 10 } { - idle_watcher.stop(); - idle_watcher.close(||()); - } else { - unsafe { *count_ptr = *count_ptr + 1; } - } - } - loop_.run(); - loop_.close(); - assert_eq!(count, 10); - } -} - -#[test] -fn idle_start_stop_start() { - do run_in_bare_thread { - let mut loop_ = Loop::new(); - let mut idle_watcher = { IdleWatcher::new(&mut loop_) }; - do idle_watcher.start |idle_watcher, status| { - let mut idle_watcher = idle_watcher; - assert!(status.is_none()); - idle_watcher.stop(); - do idle_watcher.start |idle_watcher, status| { - assert!(status.is_none()); - let mut idle_watcher = idle_watcher; - idle_watcher.stop(); - idle_watcher.close(||()); - } - } - loop_.run(); - loop_.close(); - } -} From 807269041437411df49a9a893c86310283d6eb91 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 20 May 2013 18:16:09 -0700 Subject: [PATCH 02/55] core::rt: Add bindings for async uv handles --- src/libcore/rt/uv/async.rs | 105 +++++++++++++++++++++++++++++++++++++ src/libcore/rt/uv/mod.rs | 9 +++- 2 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/libcore/rt/uv/async.rs diff --git a/src/libcore/rt/uv/async.rs b/src/libcore/rt/uv/async.rs new file mode 100644 index 000000000000..0d032f512d38 --- /dev/null +++ b/src/libcore/rt/uv/async.rs @@ -0,0 +1,105 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc::{c_int, c_void}; +use option::Some; +use rt::uv::uvll; +use rt::uv::uvll::UV_ASYNC; +use rt::uv::{Watcher, Loop, NativeHandle, AsyncCallback, NullCallback}; +use rt::uv::WatcherInterop; +use rt::uv::status_to_maybe_uv_error; + +pub struct AsyncWatcher(*uvll::uv_async_t); +impl Watcher for AsyncWatcher { } + +impl AsyncWatcher { + fn new(loop_: &mut Loop, cb: AsyncCallback) -> AsyncWatcher { + unsafe { + let handle = uvll::malloc_handle(UV_ASYNC); + assert!(handle.is_not_null()); + let mut watcher: AsyncWatcher = NativeHandle::from_native_handle(handle); + watcher.install_watcher_data(); + let data = watcher.get_watcher_data(); + data.async_cb = Some(cb); + assert_eq!(0, uvll::async_init(loop_.native_handle(), handle, async_cb)); + return watcher; + } + + extern fn async_cb(handle: *uvll::uv_async_t, status: c_int) { + let mut watcher: AsyncWatcher = NativeHandle::from_native_handle(handle); + let status = status_to_maybe_uv_error(watcher.native_handle(), status); + let data = watcher.get_watcher_data(); + let cb = data.async_cb.get_ref(); + (*cb)(watcher, status); + } + } + + fn send(&mut self) { + unsafe { + let handle = self.native_handle(); + uvll::async_send(handle); + } + } + + fn close(self, cb: NullCallback) { + let mut this = self; + let data = this.get_watcher_data(); + assert!(data.close_cb.is_none()); + data.close_cb = Some(cb); + + unsafe { + uvll::close(self.native_handle(), close_cb); + } + + extern fn close_cb(handle: *uvll::uv_stream_t) { + let mut watcher: AsyncWatcher = NativeHandle::from_native_handle(handle); + { + let data = watcher.get_watcher_data(); + data.close_cb.swap_unwrap()(); + } + watcher.drop_watcher_data(); + unsafe { uvll::free_handle(handle as *c_void); } + } + } +} + +impl NativeHandle<*uvll::uv_async_t> for AsyncWatcher { + fn from_native_handle(handle: *uvll::uv_async_t) -> AsyncWatcher { + AsyncWatcher(handle) + } + fn native_handle(&self) -> *uvll::uv_async_t { + match self { &AsyncWatcher(ptr) => ptr } + } +} + +#[cfg(test)] +mod test { + + use super::*; + use rt::uv::Loop; + use unstable::run_in_bare_thread; + use rt::thread::Thread; + use cell::Cell; + + #[test] + fn smoke_test() { + do run_in_bare_thread { + let mut loop_ = Loop::new(); + let watcher = AsyncWatcher::new(&mut loop_, |w, _| w.close(||()) ); + let watcher_cell = Cell(watcher); + let _thread = do Thread::start { + let mut watcher = watcher_cell.take(); + watcher.send(); + }; + loop_.run(); + loop_.close(); + } + } +} diff --git a/src/libcore/rt/uv/mod.rs b/src/libcore/rt/uv/mod.rs index 8cc596b2876d..5f9e56608149 100644 --- a/src/libcore/rt/uv/mod.rs +++ b/src/libcore/rt/uv/mod.rs @@ -57,6 +57,7 @@ pub use self::file::FsRequest; pub use self::net::{StreamWatcher, TcpWatcher}; pub use self::idle::IdleWatcher; pub use self::timer::TimerWatcher; +pub use self::async::AsyncWatcher; /// The implementation of `rtio` for libuv pub mod uvio; @@ -68,6 +69,7 @@ pub mod file; pub mod net; pub mod idle; pub mod timer; +pub mod async; /// XXX: Loop(*handle) is buggy with destructors. Normal structs /// with dtors may not be destructured, but tuple structs can, @@ -125,6 +127,7 @@ pub type IdleCallback = ~fn(IdleWatcher, Option); pub type ConnectionCallback = ~fn(StreamWatcher, Option); pub type FsCallback = ~fn(FsRequest, Option); pub type TimerCallback = ~fn(TimerWatcher, Option); +pub type AsyncCallback = ~fn(AsyncWatcher, Option); /// Callbacks used by StreamWatchers, set as custom data on the foreign handle @@ -135,7 +138,8 @@ struct WatcherData { close_cb: Option, alloc_cb: Option, idle_cb: Option, - timer_cb: Option + timer_cb: Option, + async_cb: Option } pub trait WatcherInterop { @@ -164,7 +168,8 @@ impl> WatcherInterop for W { close_cb: None, alloc_cb: None, idle_cb: None, - timer_cb: None + timer_cb: None, + async_cb: None }; let data = transmute::<~WatcherData, *c_void>(data); uvll::set_data_for_uv_handle(self.native_handle(), data); From 6d8d73cfc4cba2fdb2ee67448df39d89be08ce69 Mon Sep 17 00:00:00 2001 From: James Miller Date: Tue, 21 May 2013 17:31:24 +1200 Subject: [PATCH 03/55] Add AtomicUint newtype --- src/libcore/unstable/sync.rs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/libcore/unstable/sync.rs b/src/libcore/unstable/sync.rs index 734368c70c4a..7c228ff56477 100644 --- a/src/libcore/unstable/sync.rs +++ b/src/libcore/unstable/sync.rs @@ -205,6 +205,26 @@ extern { fn rust_unlock_little_lock(lock: rust_little_lock); } +/* *********************************************************************/ + +//FIXME: #5042 This should be replaced by proper atomic type +pub struct AtomicUint(uint); +pub impl AtomicUint { + fn load(&self) -> uint { + unsafe { intrinsics::atomic_load(cast::transmute(self)) as uint } + } + fn store(&mut self, val:uint) { + unsafe { intrinsics::atomic_store(cast::transmute(self), val as int); } + } + fn add(&mut self, val:int) -> uint { + unsafe { intrinsics::atomic_xadd(cast::transmute(self), val as int) as uint } + } + fn cas(&self, old:uint, new:uint) -> uint { + unsafe { intrinsics::atomic_cxchg(cast::transmute(self), old as int, new as int) as uint } + } +} + + #[cfg(test)] mod tests { use comm; From 8f77a6f422184dacc14ae1b6a042c321e06bef88 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 21 May 2013 17:36:59 -0700 Subject: [PATCH 04/55] core: Add AtomicInt and cleanup --- src/libcore/unstable/sync.rs | 61 ++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/src/libcore/unstable/sync.rs b/src/libcore/unstable/sync.rs index 7c228ff56477..6085ca1a482e 100644 --- a/src/libcore/unstable/sync.rs +++ b/src/libcore/unstable/sync.rs @@ -208,25 +208,50 @@ extern { /* *********************************************************************/ //FIXME: #5042 This should be replaced by proper atomic type -pub struct AtomicUint(uint); -pub impl AtomicUint { - fn load(&self) -> uint { +pub struct AtomicUint { + priv inner: uint +} + +impl AtomicUint { + pub fn new(val: uint) -> AtomicUint { AtomicUint { inner: val } } + pub fn load(&self) -> uint { unsafe { intrinsics::atomic_load(cast::transmute(self)) as uint } } - fn store(&mut self, val:uint) { + pub fn store(&mut self, val: uint) { unsafe { intrinsics::atomic_store(cast::transmute(self), val as int); } } - fn add(&mut self, val:int) -> uint { + pub fn add(&mut self, val: int) -> uint { unsafe { intrinsics::atomic_xadd(cast::transmute(self), val as int) as uint } } - fn cas(&self, old:uint, new:uint) -> uint { + pub fn cas(&mut self, old:uint, new: uint) -> uint { unsafe { intrinsics::atomic_cxchg(cast::transmute(self), old as int, new as int) as uint } } } +pub struct AtomicInt { + priv inner: int +} + +impl AtomicInt { + pub fn new(val: int) -> AtomicInt { AtomicInt { inner: val } } + pub fn load(&self) -> int { + unsafe { intrinsics::atomic_load(&self.inner) } + } + pub fn store(&mut self, val: int) { + unsafe { intrinsics::atomic_store(&mut self.inner, val); } + } + pub fn add(&mut self, val: int) -> int { + unsafe { intrinsics::atomic_xadd(&mut self.inner, val) } + } + pub fn cas(&mut self, old: int, new: int) -> int { + unsafe { intrinsics::atomic_cxchg(&mut self.inner, old, new) } + } +} + #[cfg(test)] mod tests { + use super::*; use comm; use super::exclusive; use task; @@ -278,4 +303,28 @@ mod tests { assert_eq!(*one, 1); } } + + #[test] + fn atomic_int_smoke_test() { + let mut i = AtomicInt::new(0); + i.store(10); + assert!(i.load() == 10); + assert!(i.add(1) == 10); + assert!(i.load() == 11); + assert!(i.cas(11, 12) == 11); + assert!(i.cas(11, 13) == 12); + assert!(i.load() == 12); + } + + #[test] + fn atomic_uint_smoke_test() { + let mut i = AtomicUint::new(0); + i.store(10); + assert!(i.load() == 10); + assert!(i.add(1) == 10); + assert!(i.load() == 11); + assert!(i.cas(11, 12) == 11); + assert!(i.cas(11, 13) == 12); + assert!(i.load() == 12); + } } From a0cd55a1d7436dc9532ddf5cdad7d1f7e8f108f3 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 20 May 2013 18:23:56 -0700 Subject: [PATCH 05/55] core::rt: Add RemoteCallback trait and uv implementation This is used for signalling the event loop from other threads. --- src/libcore/rt/rtio.rs | 11 +++++ src/libcore/rt/uv/async.rs | 6 +-- src/libcore/rt/uv/uvio.rs | 86 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 3 deletions(-) diff --git a/src/libcore/rt/rtio.rs b/src/libcore/rt/rtio.rs index 4b5eda22ff5d..fa657555f3aa 100644 --- a/src/libcore/rt/rtio.rs +++ b/src/libcore/rt/rtio.rs @@ -18,6 +18,7 @@ use rt::uv::uvio; // XXX: ~object doesn't work currently so these are some placeholder // types to use instead pub type EventLoopObject = uvio::UvEventLoop; +pub type RemoteCallbackObject = uvio::UvRemoteCallback; pub type IoFactoryObject = uvio::UvIoFactory; pub type RtioTcpStreamObject = uvio::UvTcpStream; pub type RtioTcpListenerObject = uvio::UvTcpListener; @@ -26,10 +27,20 @@ pub trait EventLoop { fn run(&mut self); fn callback(&mut self, ~fn()); fn callback_ms(&mut self, ms: u64, ~fn()); + fn remote_callback(&mut self, ~fn()) -> ~RemoteCallbackObject; /// The asynchronous I/O services. Not all event loops may provide one fn io<'a>(&'a mut self) -> Option<&'a mut IoFactoryObject>; } +pub trait RemoteCallback { + /// Trigger the remote callback. Note that the number of times the callback + /// is run is not guaranteed. All that is guaranteed is that, after calling 'fire', + /// the callback will be called at least once, but multiple callbacks may be coalesced + /// and callbacks may be called more often requested. Destruction also triggers the + /// callback. + fn fire(&mut self); +} + pub trait IoFactory { fn tcp_connect(&mut self, addr: IpAddr) -> Result<~RtioTcpStreamObject, IoError>; fn tcp_bind(&mut self, addr: IpAddr) -> Result<~RtioTcpListenerObject, IoError>; diff --git a/src/libcore/rt/uv/async.rs b/src/libcore/rt/uv/async.rs index 0d032f512d38..6ed06cc10b78 100644 --- a/src/libcore/rt/uv/async.rs +++ b/src/libcore/rt/uv/async.rs @@ -20,7 +20,7 @@ pub struct AsyncWatcher(*uvll::uv_async_t); impl Watcher for AsyncWatcher { } impl AsyncWatcher { - fn new(loop_: &mut Loop, cb: AsyncCallback) -> AsyncWatcher { + pub fn new(loop_: &mut Loop, cb: AsyncCallback) -> AsyncWatcher { unsafe { let handle = uvll::malloc_handle(UV_ASYNC); assert!(handle.is_not_null()); @@ -41,14 +41,14 @@ impl AsyncWatcher { } } - fn send(&mut self) { + pub fn send(&mut self) { unsafe { let handle = self.native_handle(); uvll::async_send(handle); } } - fn close(self, cb: NullCallback) { + pub fn close(self, cb: NullCallback) { let mut this = self; let data = this.get_watcher_data(); assert!(data.close_cb.is_none()); diff --git a/src/libcore/rt/uv/uvio.rs b/src/libcore/rt/uv/uvio.rs index cacd67314eba..cf1bd568d028 100644 --- a/src/libcore/rt/uv/uvio.rs +++ b/src/libcore/rt/uv/uvio.rs @@ -12,6 +12,7 @@ use option::*; use result::*; use ops::Drop; use cell::{Cell, empty_cell}; +use cast; use cast::transmute; use clone::Clone; use rt::io::IoError; @@ -23,6 +24,8 @@ use rt::sched::Scheduler; use rt::io::{standard_error, OtherIoError}; use rt::tube::Tube; use rt::local::Local; +use unstable::sync::{UnsafeAtomicRcBox, AtomicInt}; +use unstable::intrinsics; #[cfg(test)] use container::Container; #[cfg(test)] use uint; @@ -82,6 +85,10 @@ impl EventLoop for UvEventLoop { } } + fn remote_callback(&mut self, f: ~fn()) -> ~RemoteCallbackObject { + ~UvRemoteCallback::new(self.uvio.uv_loop(), f) + } + fn io<'a>(&'a mut self) -> Option<&'a mut IoFactoryObject> { Some(&mut self.uvio) } @@ -101,6 +108,85 @@ fn test_callback_run_once() { } } +pub struct UvRemoteCallback { + // The uv async handle for triggering the callback + async: AsyncWatcher, + // An atomic flag to tell the callback to exit, + // set from the dtor. + exit_flag: UnsafeAtomicRcBox +} + +impl UvRemoteCallback { + pub fn new(loop_: &mut Loop, f: ~fn()) -> UvRemoteCallback { + let exit_flag = UnsafeAtomicRcBox::new(AtomicInt::new(0)); + let exit_flag_clone = exit_flag.clone(); + let async = do AsyncWatcher::new(loop_) |watcher, status| { + assert!(status.is_none()); + f(); + let exit_flag_ptr = exit_flag_clone.get(); + unsafe { + if (*exit_flag_ptr).load() == 1 { + watcher.close(||()); + } + } + }; + UvRemoteCallback { + async: async, + exit_flag: exit_flag + } + } +} + +impl RemoteCallback for UvRemoteCallback { + fn fire(&mut self) { self.async.send() } +} + +impl Drop for UvRemoteCallback { + fn finalize(&self) { + unsafe { + let mut this: &mut UvRemoteCallback = cast::transmute_mut(self); + let exit_flag_ptr = this.exit_flag.get(); + (*exit_flag_ptr).store(1); + this.async.send(); + } + } +} + +#[cfg(test)] +mod test_remote { + use super::*; + use cell; + use cell::Cell; + use rt::test::*; + use rt::thread::Thread; + use rt::tube::Tube; + use rt::rtio::EventLoop; + use rt::local::Local; + use rt::sched::Scheduler; + + #[test] + fn test_uv_remote() { + do run_in_newsched_task { + let mut tube = Tube::new(); + let tube_clone = tube.clone(); + let remote_cell = cell::empty_cell(); + do Local::borrow::() |sched| { + let tube_clone = tube_clone.clone(); + let tube_clone_cell = Cell(tube_clone); + let remote = do sched.event_loop.remote_callback { + tube_clone_cell.take().send(1); + }; + remote_cell.put_back(remote); + } + let _thread = do Thread::start { + remote_cell.take().fire(); + }; + + assert!(tube.recv() == 1); + } + } +} + pub struct UvIoFactory(Loop); pub impl UvIoFactory { From 41c21685dd149fb95dededfb4edaf87c6603c099 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 22 May 2013 15:39:39 -0700 Subject: [PATCH 06/55] core::rt: Add SchedHandle type --- src/libcore/rt/sched.rs | 101 +++++++++++++++++++++++++++++++--------- 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index 50c6a894093f..3f7b332e184b 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -15,7 +15,7 @@ use cell::Cell; use super::work_queue::WorkQueue; use super::stack::{StackPool, StackSegment}; -use super::rtio::{EventLoop, EventLoopObject}; +use super::rtio::{EventLoop, EventLoopObject, RemoteCallbackObject}; use super::context::Context; use super::task::Task; use rt::local_ptr; @@ -41,16 +41,19 @@ pub struct Scheduler { priv cleanup_job: Option } -// XXX: Some hacks to put a &fn in Scheduler without borrowck -// complaining -type UnsafeTaskReceiver = sys::Closure; -trait ClosureConverter { - fn from_fn(&fn(~Coroutine)) -> Self; - fn to_fn(self) -> &fn(~Coroutine); +pub struct Coroutine { + /// The segment of stack on which the task is currently running or, + /// if the task is blocked, on which the task will resume execution + priv current_stack_segment: StackSegment, + /// These are always valid when the task is not running, unless + /// the task is dead + priv saved_context: Context, + /// The heap, GC, unwinding, local storage, logging + task: ~Task } -impl ClosureConverter for UnsafeTaskReceiver { - fn from_fn(f: &fn(~Coroutine)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } - fn to_fn(self) -> &fn(~Coroutine) { unsafe { transmute(self) } } + +pub struct SchedHandle { + priv remote: ~RemoteCallbackObject } enum CleanupJob { @@ -103,6 +106,17 @@ pub impl Scheduler { return sched; } + fn make_handle(&mut self) -> SchedHandle { + let remote = self.event_loop.remote_callback(wake_up); + + return SchedHandle { + remote: remote + }; + + fn wake_up() { + } + } + /// Schedule a task to be executed later. /// /// Pushes the task onto the work stealing queue and tells the event loop @@ -337,19 +351,6 @@ pub impl Scheduler { } } -static MIN_STACK_SIZE: uint = 10000000; // XXX: Too much stack - -pub struct Coroutine { - /// The segment of stack on which the task is currently running or, - /// if the task is blocked, on which the task will resume execution - priv current_stack_segment: StackSegment, - /// These are always valid when the task is not running, unless - /// the task is dead - priv saved_context: Context, - /// The heap, GC, unwinding, local storage, logging - task: ~Task -} - pub impl Coroutine { fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { Coroutine::with_task(stack_pool, ~Task::new(), start) @@ -358,6 +359,9 @@ pub impl Coroutine { fn with_task(stack_pool: &mut StackPool, task: ~Task, start: ~fn()) -> Coroutine { + + static MIN_STACK_SIZE: uint = 10000000; // XXX: Too much stack + let start = Coroutine::build_start_wrapper(start); let mut stack = stack_pool.take_segment(MIN_STACK_SIZE); // NB: Context holds a pointer to that ~fn @@ -401,6 +405,18 @@ pub impl Coroutine { } } +// XXX: Some hacks to put a &fn in Scheduler without borrowck +// complaining +type UnsafeTaskReceiver = sys::Closure; +trait ClosureConverter { + fn from_fn(&fn(~Coroutine)) -> Self; + fn to_fn(self) -> &fn(~Coroutine); +} +impl ClosureConverter for UnsafeTaskReceiver { + fn from_fn(f: &fn(~Coroutine)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } + fn to_fn(self) -> &fn(~Coroutine) { unsafe { transmute(self) } } +} + #[cfg(test)] mod test { use int; @@ -411,6 +427,7 @@ mod test { use rt::local::Local; use rt::test::*; use super::*; + use rt::thread::Thread; #[test] fn test_simple_scheduling() { @@ -551,4 +568,42 @@ mod test { } } } + + #[test] + fn handle() { + use rt::comm::*; + + do run_in_bare_thread { + let (port, chan) = oneshot::<()>(); + let port_cell = Cell(port); + let chan_cell = Cell(chan); + let mut sched1 = ~UvEventLoop::new_scheduler(); + let handle1 = sched1.make_handle(); + let handle1_cell = Cell(handle1); + let task1 = ~do Coroutine::new(&mut sched1.stack_pool) { + chan_cell.take().send(()); + }; + sched1.enqueue_task(task1); + + let mut sched2 = ~UvEventLoop::new_scheduler(); + let task2 = ~do Coroutine::new(&mut sched2.stack_pool) { + port_cell.take().recv(); + // Release the other scheduler's handle so it can exit + handle1_cell.take(); + }; + sched2.enqueue_task(task2); + + let sched1_cell = Cell(sched1); + let _thread1 = do Thread::start { + let mut sched1 = sched1_cell.take(); + sched1.run(); + }; + + let sched2_cell = Cell(sched2); + let _thread2 = do Thread::start { + let mut sched2 = sched2_cell.take(); + sched2.run(); + }; + } + } } From 8b7e392752eddc202dae12c6b89b7c59556990ce Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 22 May 2013 21:20:19 -0700 Subject: [PATCH 07/55] core::rt: Scheduler takes a WorkQueue This will be for implementing a work-sharing strategy --- src/libcore/rt/mod.rs | 4 +++- src/libcore/rt/sched.rs | 4 ++-- src/libcore/rt/uv/uvio.rs | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index 2fac1df01a49..b6abab38da79 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -145,12 +145,14 @@ pub mod thread_local_storage; pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { use self::sched::{Scheduler, Coroutine}; + use self::work_queue::WorkQueue; use self::uv::uvio::UvEventLoop; init(crate_map); let loop_ = ~UvEventLoop::new(); - let mut sched = ~Scheduler::new(loop_); + let work_queue = WorkQueue::new(); + let mut sched = ~Scheduler::new(loop_, work_queue); let main_task = ~Coroutine::new(&mut sched.stack_pool, main); sched.enqueue_task(main_task); diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index 3f7b332e184b..f1670d4896a4 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -65,14 +65,14 @@ pub impl Scheduler { fn in_task_context(&self) -> bool { self.current_task.is_some() } - fn new(event_loop: ~EventLoopObject) -> Scheduler { + fn new(event_loop: ~EventLoopObject, work_queue: WorkQueue<~Coroutine>) -> Scheduler { // Lazily initialize the runtime TLS key local_ptr::init_tls_key(); Scheduler { event_loop: event_loop, - work_queue: WorkQueue::new(), + work_queue: work_queue, stack_pool: StackPool::new(), saved_context: Context::empty(), current_task: None, diff --git a/src/libcore/rt/uv/uvio.rs b/src/libcore/rt/uv/uvio.rs index cf1bd568d028..793a341bffbf 100644 --- a/src/libcore/rt/uv/uvio.rs +++ b/src/libcore/rt/uv/uvio.rs @@ -24,6 +24,7 @@ use rt::sched::Scheduler; use rt::io::{standard_error, OtherIoError}; use rt::tube::Tube; use rt::local::Local; +use rt::work_queue::WorkQueue; use unstable::sync::{UnsafeAtomicRcBox, AtomicInt}; use unstable::intrinsics; @@ -45,7 +46,7 @@ pub impl UvEventLoop { /// A convenience constructor fn new_scheduler() -> Scheduler { - Scheduler::new(~UvEventLoop::new()) + Scheduler::new(~UvEventLoop::new(), WorkQueue::new()) } } From 7f107c415f1c88b016b9da0fa9c58e6b61f82589 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 22 May 2013 22:18:29 -0700 Subject: [PATCH 08/55] core::rt: Remove UvEventLoop::new_scheduler function --- src/libcore/rt/local.rs | 9 +++++---- src/libcore/rt/mod.rs | 3 ++- src/libcore/rt/sched.rs | 14 +++++++------- src/libcore/rt/test.rs | 12 ++++++++++-- src/libcore/rt/uv/uvio.rs | 5 ----- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/libcore/rt/local.rs b/src/libcore/rt/local.rs index 64a384ddff0b..b4ecf9cd0616 100644 --- a/src/libcore/rt/local.rs +++ b/src/libcore/rt/local.rs @@ -85,30 +85,31 @@ impl Local for IoFactoryObject { #[cfg(test)] mod test { + use rt::test::*; use rt::sched::Scheduler; use rt::uv::uvio::UvEventLoop; use super::*; #[test] fn thread_local_scheduler_smoke_test() { - let scheduler = ~UvEventLoop::new_scheduler(); + let scheduler = ~new_test_uv_sched(); Local::put(scheduler); let _scheduler: ~Scheduler = Local::take(); } #[test] fn thread_local_scheduler_two_instances() { - let scheduler = ~UvEventLoop::new_scheduler(); + let scheduler = ~new_test_uv_sched(); Local::put(scheduler); let _scheduler: ~Scheduler = Local::take(); - let scheduler = ~UvEventLoop::new_scheduler(); + let scheduler = ~new_test_uv_sched(); Local::put(scheduler); let _scheduler: ~Scheduler = Local::take(); } #[test] fn borrow_smoke_test() { - let scheduler = ~UvEventLoop::new_scheduler(); + let scheduler = ~new_test_uv_sched(); Local::put(scheduler); unsafe { let _scheduler: *mut Scheduler = Local::unsafe_borrow(); diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index b6abab38da79..f136732c00b9 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -223,11 +223,12 @@ fn test_context() { use rt::uv::uvio::UvEventLoop; use cell::Cell; use rt::local::Local; + use rt::test::new_test_uv_sched; assert_eq!(context(), OldTaskContext); do run_in_bare_thread { assert_eq!(context(), GlobalContext); - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task = ~do Coroutine::new(&mut sched.stack_pool) { assert_eq!(context(), TaskContext); let sched = Local::take::(); diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index f1670d4896a4..78c5da08c39b 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -435,7 +435,7 @@ mod test { let mut task_ran = false; let task_ran_ptr: *mut bool = &mut task_ran; - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task = ~do Coroutine::new(&mut sched.stack_pool) { unsafe { *task_ran_ptr = true; } }; @@ -452,7 +452,7 @@ mod test { let mut task_count = 0; let task_count_ptr: *mut int = &mut task_count; - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); for int::range(0, total) |_| { let task = ~do Coroutine::new(&mut sched.stack_pool) { unsafe { *task_count_ptr = *task_count_ptr + 1; } @@ -470,7 +470,7 @@ mod test { let mut count = 0; let count_ptr: *mut int = &mut count; - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task1 = ~do Coroutine::new(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; } let mut sched = Local::take::(); @@ -499,7 +499,7 @@ mod test { let mut count = 0; let count_ptr: *mut int = &mut count; - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let start_task = ~do Coroutine::new(&mut sched.stack_pool) { run_task(count_ptr); @@ -528,7 +528,7 @@ mod test { #[test] fn test_block_task() { do run_in_bare_thread { - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task = ~do Coroutine::new(&mut sched.stack_pool) { let sched = Local::take::(); assert!(sched.in_task_context()); @@ -577,7 +577,7 @@ mod test { let (port, chan) = oneshot::<()>(); let port_cell = Cell(port); let chan_cell = Cell(chan); - let mut sched1 = ~UvEventLoop::new_scheduler(); + let mut sched1 = ~new_test_uv_sched(); let handle1 = sched1.make_handle(); let handle1_cell = Cell(handle1); let task1 = ~do Coroutine::new(&mut sched1.stack_pool) { @@ -585,7 +585,7 @@ mod test { }; sched1.enqueue_task(task1); - let mut sched2 = ~UvEventLoop::new_scheduler(); + let mut sched2 = ~new_test_uv_sched(); let task2 = ~do Coroutine::new(&mut sched2.stack_pool) { port_cell.take().recv(); // Release the other scheduler's handle so it can exit diff --git a/src/libcore/rt/test.rs b/src/libcore/rt/test.rs index c60ae2bfeffc..0e2da452366c 100644 --- a/src/libcore/rt/test.rs +++ b/src/libcore/rt/test.rs @@ -16,6 +16,14 @@ use super::io::net::ip::{IpAddr, Ipv4}; use rt::task::Task; use rt::thread::Thread; use rt::local::Local; +use rt::sched::Scheduler; + +pub fn new_test_uv_sched() -> Scheduler { + use rt::uv::uvio::UvEventLoop; + use rt::work_queue::WorkQueue; + + Scheduler::new(~UvEventLoop::new(), WorkQueue::new()) +} /// Creates a new scheduler in a new thread and runs a task in it, /// then waits for the scheduler to exit. Failure of the task @@ -28,7 +36,7 @@ pub fn run_in_newsched_task(f: ~fn()) { let f = Cell(f); do run_in_bare_thread { - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task = ~Coroutine::with_task(&mut sched.stack_pool, ~Task::without_unwinding(), f.take()); @@ -155,7 +163,7 @@ pub fn spawntask_thread(f: ~fn()) -> Thread { let f = Cell(f); let thread = do Thread::start { - let mut sched = ~UvEventLoop::new_scheduler(); + let mut sched = ~new_test_uv_sched(); let task = ~Coroutine::with_task(&mut sched.stack_pool, ~Task::without_unwinding(), f.take()); diff --git a/src/libcore/rt/uv/uvio.rs b/src/libcore/rt/uv/uvio.rs index 793a341bffbf..e25b6140abbf 100644 --- a/src/libcore/rt/uv/uvio.rs +++ b/src/libcore/rt/uv/uvio.rs @@ -43,11 +43,6 @@ pub impl UvEventLoop { uvio: UvIoFactory(Loop::new()) } } - - /// A convenience constructor - fn new_scheduler() -> Scheduler { - Scheduler::new(~UvEventLoop::new(), WorkQueue::new()) - } } impl Drop for UvEventLoop { From 3f8095e55043f35e08adba5fe5b0a2d687ebc514 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 23 May 2013 00:04:50 -0700 Subject: [PATCH 09/55] core::rt: Add a very basic multi-threaded scheduling test --- src/libcore/rt/sched.rs | 72 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index 78c5da08c39b..e78d50beebe1 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -114,6 +114,8 @@ pub impl Scheduler { }; fn wake_up() { + let sched = Local::take::(); + sched.resume_task_from_queue(); } } @@ -127,8 +129,8 @@ pub impl Scheduler { self.event_loop.callback(resume_task_from_queue); fn resume_task_from_queue() { - let scheduler = Local::take::(); - scheduler.resume_task_from_queue(); + let sched = Local::take::(); + sched.resume_task_from_queue(); } } @@ -606,4 +608,70 @@ mod test { }; } } + + #[test] + fn multithreading() { + use clone::Clone; + use iter::Times; + use rt::work_queue::WorkQueue; + use rt::comm::*; + use container::Container; + use vec::OwnedVector; + use rt::rtio::RemoteCallback; + + do run_in_bare_thread { + let work_queue1 = WorkQueue::new(); + let work_queue2 = work_queue1.clone(); + + let loop1 = ~UvEventLoop::new(); + let mut sched1 = ~Scheduler::new(loop1, work_queue1.clone()); + let handle1 = sched1.make_handle(); + let sched1_cell = Cell(sched1); + let handle1_cell = Cell(handle1); + + let loop2 = ~UvEventLoop::new(); + let mut sched2 = ~Scheduler::new(loop2, work_queue2.clone()); + let handle2 = sched2.make_handle(); + let sched2_cell = Cell(sched2); + let handle2_cell = Cell(handle2); + + let _thread1 = do Thread::start { + let mut sched1 = sched1_cell.take(); + sched1.run(); + }; + + let _thread2 = do Thread::start { + let mut sched2 = sched2_cell.take(); + let handle1_cell = Cell(handle1_cell.take()); + let handle2_cell = Cell(handle2_cell.take()); + + let task = ~do Coroutine::new(&mut sched2.stack_pool) { + // Hold handles to keep the schedulers alive + let mut handle1 = handle1_cell.take(); + let mut handle2 = handle2_cell.take(); + + let mut ports = ~[]; + for 10.times { + let (port, chan) = oneshot(); + let chan_cell = Cell(chan); + do spawntask_later { + chan_cell.take().send(()); + } + ports.push(port); + + // Make sure the other scheduler is awake + handle1.remote.fire(); + handle2.remote.fire(); + } + + while !ports.is_empty() { + ports.pop().recv(); + } + }; + + sched2.enqueue_task(task); + sched2.run(); + }; + } + } } From dec9db10da062b1c528d46426d9f62e201d39bc6 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 May 2013 18:39:52 -0700 Subject: [PATCH 10/55] core::rt: Add SleeperList Just a simple place to stuff handles to sleeping schedulers. --- src/libcore/rt/mod.rs | 3 +++ src/libcore/rt/sleeper_list.rs | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 src/libcore/rt/sleeper_list.rs diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index f136732c00b9..82496ec55894 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -88,6 +88,9 @@ mod work_queue; /// A parallel queue. mod message_queue; +/// A parallel data structure for tracking sleeping schedulers. +mod sleeper_list; + /// Stack segments and caching. mod stack; diff --git a/src/libcore/rt/sleeper_list.rs b/src/libcore/rt/sleeper_list.rs new file mode 100644 index 000000000000..9507dec001d5 --- /dev/null +++ b/src/libcore/rt/sleeper_list.rs @@ -0,0 +1,46 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Maintains a shared list of sleeping schedulers. Schedulers +//! use this to wake each other up. + +use container::Container; +use vec::OwnedVector; +use option::{Option, Some, None}; +use cell::Cell; +use unstable::sync::{Exclusive, exclusive}; +use rt::sched::{Scheduler, SchedHandle}; + +pub struct SleeperList { + priv stack: ~Exclusive<~[SchedHandle]> +} + +impl SleeperList { + pub fn new() -> SleeperList { + SleeperList { + stack: ~exclusive(~[]) + } + } + + pub fn push(&mut self, handle: SchedHandle) { + let handle = Cell(handle); + self.stack.with(|s| s.push(handle.take())); + } + + pub fn pop(&mut self) -> Option { + do self.stack.with |s| { + if !s.is_empty() { + Some(s.pop()) + } else { + None + } + } + } +} From ed8c3594bc86dd366e729d02c34915c783e6ac81 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 May 2013 19:53:55 -0700 Subject: [PATCH 11/55] core::rt: Add SleeperList to Scheduler --- src/libcore/rt/mod.rs | 4 +++- src/libcore/rt/sched.rs | 20 +++++++++++++++++--- src/libcore/rt/sleeper_list.rs | 9 +++++++++ src/libcore/rt/test.rs | 3 ++- 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index 82496ec55894..75036dcd28f8 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -150,12 +150,14 @@ pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { use self::sched::{Scheduler, Coroutine}; use self::work_queue::WorkQueue; use self::uv::uvio::UvEventLoop; + use self::sleeper_list::SleeperList; init(crate_map); let loop_ = ~UvEventLoop::new(); let work_queue = WorkQueue::new(); - let mut sched = ~Scheduler::new(loop_, work_queue); + let sleepers = SleeperList::new(); + let mut sched = ~Scheduler::new(loop_, work_queue, sleepers); let main_task = ~Coroutine::new(&mut sched.stack_pool, main); sched.enqueue_task(main_task); diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index e78d50beebe1..2a99648fa045 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -13,6 +13,7 @@ use sys; use cast::transmute; use cell::Cell; +use super::sleeper_list::SleeperList; use super::work_queue::WorkQueue; use super::stack::{StackPool, StackSegment}; use super::rtio::{EventLoop, EventLoopObject, RemoteCallbackObject}; @@ -27,7 +28,12 @@ use rt::rtio::IoFactoryObject; /// thread local storage and the running task is owned by the /// scheduler. pub struct Scheduler { + /// A queue of available work. Under a work-stealing policy there + /// is one per Scheduler. priv work_queue: WorkQueue<~Coroutine>, + /// A shared list of sleeping schedulers. We'll use this to wake + /// up schedulers when pushing work onto the work queue. + priv sleeper_list: SleeperList, stack_pool: StackPool, /// The event loop used to drive the scheduler and perform I/O event_loop: ~EventLoopObject, @@ -65,12 +71,16 @@ pub impl Scheduler { fn in_task_context(&self) -> bool { self.current_task.is_some() } - fn new(event_loop: ~EventLoopObject, work_queue: WorkQueue<~Coroutine>) -> Scheduler { + fn new(event_loop: ~EventLoopObject, + work_queue: WorkQueue<~Coroutine>, + sleeper_list: SleeperList) + -> Scheduler { // Lazily initialize the runtime TLS key local_ptr::init_tls_key(); Scheduler { + sleeper_list: sleeper_list, event_loop: event_loop, work_queue: work_queue, stack_pool: StackPool::new(), @@ -618,19 +628,23 @@ mod test { use container::Container; use vec::OwnedVector; use rt::rtio::RemoteCallback; + use rt::sleeper_list::SleeperList; do run_in_bare_thread { + let sleepers1 = SleeperList::new(); let work_queue1 = WorkQueue::new(); + + let sleepers2 = sleepers1.clone(); let work_queue2 = work_queue1.clone(); let loop1 = ~UvEventLoop::new(); - let mut sched1 = ~Scheduler::new(loop1, work_queue1.clone()); + let mut sched1 = ~Scheduler::new(loop1, work_queue1.clone(), sleepers1); let handle1 = sched1.make_handle(); let sched1_cell = Cell(sched1); let handle1_cell = Cell(handle1); let loop2 = ~UvEventLoop::new(); - let mut sched2 = ~Scheduler::new(loop2, work_queue2.clone()); + let mut sched2 = ~Scheduler::new(loop2, work_queue2.clone(), sleepers2); let handle2 = sched2.make_handle(); let sched2_cell = Cell(sched2); let handle2_cell = Cell(handle2); diff --git a/src/libcore/rt/sleeper_list.rs b/src/libcore/rt/sleeper_list.rs index 9507dec001d5..dfcac8eb088f 100644 --- a/src/libcore/rt/sleeper_list.rs +++ b/src/libcore/rt/sleeper_list.rs @@ -17,6 +17,7 @@ use option::{Option, Some, None}; use cell::Cell; use unstable::sync::{Exclusive, exclusive}; use rt::sched::{Scheduler, SchedHandle}; +use clone::Clone; pub struct SleeperList { priv stack: ~Exclusive<~[SchedHandle]> @@ -44,3 +45,11 @@ impl SleeperList { } } } + +impl Clone for SleeperList { + fn clone(&self) -> SleeperList { + SleeperList { + stack: self.stack.clone() + } + } +} \ No newline at end of file diff --git a/src/libcore/rt/test.rs b/src/libcore/rt/test.rs index 0e2da452366c..d6896f500343 100644 --- a/src/libcore/rt/test.rs +++ b/src/libcore/rt/test.rs @@ -21,8 +21,9 @@ use rt::sched::Scheduler; pub fn new_test_uv_sched() -> Scheduler { use rt::uv::uvio::UvEventLoop; use rt::work_queue::WorkQueue; + use rt::sleeper_list::SleeperList; - Scheduler::new(~UvEventLoop::new(), WorkQueue::new()) + Scheduler::new(~UvEventLoop::new(), WorkQueue::new(), SleeperList::new()) } /// Creates a new scheduler in a new thread and runs a task in it, From 5043ea269da73e96fbadc7c443aec01f087dabe9 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 28 May 2013 23:35:22 -0700 Subject: [PATCH 12/55] core::rt: Add run_in_mt_newsched_task test function --- src/libcore/rt/test.rs | 63 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/src/libcore/rt/test.rs b/src/libcore/rt/test.rs index d6896f500343..a66e4f09fe72 100644 --- a/src/libcore/rt/test.rs +++ b/src/libcore/rt/test.rs @@ -9,14 +9,20 @@ // except according to those terms. use uint; -use option::*; +use option::{Option, Some, None}; use cell::Cell; +use clone::Clone; +use container::Container; +use vec::OwnedVector; use result::{Result, Ok, Err}; +use unstable::run_in_bare_thread; use super::io::net::ip::{IpAddr, Ipv4}; use rt::task::Task; use rt::thread::Thread; use rt::local::Local; -use rt::sched::Scheduler; +use rt::sched::{Scheduler, Coroutine}; +use rt::sleeper_list::SleeperList; +use rt::work_queue::WorkQueue; pub fn new_test_uv_sched() -> Scheduler { use rt::uv::uvio::UvEventLoop; @@ -46,6 +52,59 @@ pub fn run_in_newsched_task(f: ~fn()) { } } +/// Create more than one scheduler and run a function in a task +/// in one of the schedulers. The schedulers will stay alive +/// until the function `f` returns. +pub fn run_in_mt_newsched_task(f: ~fn()) { + use rt::uv::uvio::UvEventLoop; + + let f_cell = Cell(f); + + do run_in_bare_thread { + static N: uint = 2; + + let sleepers = SleeperList::new(); + let work_queue = WorkQueue::new(); + + let mut handles = ~[]; + let mut scheds = ~[]; + + for uint::range(0, N) |i| { + let loop_ = ~UvEventLoop::new(); + let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); + let handle = sched.make_handle(); + handles.push(handle); + scheds.push(sched); + } + + let f_cell = Cell(f_cell.take()); + let handles = handles; // Freeze + let main_task = ~do Coroutine::new(&mut scheds[0].stack_pool) { + f_cell.take()(); + // Hold on to handles until the function exits. This keeps the schedulers alive. + let _captured_handles = &handles; + }; + + scheds[0].enqueue_task(main_task); + + let mut threads = ~[]; + + while !scheds.is_empty() { + let sched = scheds.pop(); + let sched_cell = Cell(sched); + let thread = do Thread::start { + let mut sched = sched_cell.take(); + sched.run(); + }; + + threads.push(thread); + } + + // Wait for schedulers + let _threads = threads; + } +} + /// Test tasks will abort on failure instead of unwinding pub fn spawntask(f: ~fn()) { use super::sched::*; From a373dad74d0bd89a9d5362bba1059d9cc25afb9a Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 29 May 2013 15:55:23 -0700 Subject: [PATCH 13/55] core::rt: Outline the full multithreaded scheduling algo. Implement sleeping --- src/libcore/rt/message_queue.rs | 3 + src/libcore/rt/mod.rs | 1 + src/libcore/rt/sched.rs | 222 +++++++++++++++++++++----------- src/libcore/rt/test.rs | 17 ++- 4 files changed, 162 insertions(+), 81 deletions(-) diff --git a/src/libcore/rt/message_queue.rs b/src/libcore/rt/message_queue.rs index eaab9288ac8d..21711bbe84c7 100644 --- a/src/libcore/rt/message_queue.rs +++ b/src/libcore/rt/message_queue.rs @@ -8,6 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! A concurrent queue that supports multiple producers and a +//! single consumer. + use container::Container; use kinds::Owned; use vec::OwnedVector; diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index 75036dcd28f8..e23ad76a8c61 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -158,6 +158,7 @@ pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { let work_queue = WorkQueue::new(); let sleepers = SleeperList::new(); let mut sched = ~Scheduler::new(loop_, work_queue, sleepers); + sched.no_sleep = true; let main_task = ~Coroutine::new(&mut sched.stack_pool, main); sched.enqueue_task(main_task); diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index 2a99648fa045..c6d6bb9f39e5 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -12,6 +12,7 @@ use option::*; use sys; use cast::transmute; use cell::Cell; +use clone::Clone; use super::sleeper_list::SleeperList; use super::work_queue::WorkQueue; @@ -19,9 +20,10 @@ use super::stack::{StackPool, StackSegment}; use super::rtio::{EventLoop, EventLoopObject, RemoteCallbackObject}; use super::context::Context; use super::task::Task; +use super::message_queue::MessageQueue; use rt::local_ptr; use rt::local::Local; -use rt::rtio::IoFactoryObject; +use rt::rtio::{IoFactoryObject, RemoteCallback}; /// The Scheduler is responsible for coordinating execution of Coroutines /// on a single thread. When the scheduler is running it is owned by @@ -31,9 +33,23 @@ pub struct Scheduler { /// A queue of available work. Under a work-stealing policy there /// is one per Scheduler. priv work_queue: WorkQueue<~Coroutine>, + /// The queue of incoming messages from other schedulers. + /// These are enqueued by SchedHandles after which a remote callback + /// is triggered to handle the message. + priv message_queue: MessageQueue, /// A shared list of sleeping schedulers. We'll use this to wake /// up schedulers when pushing work onto the work queue. priv sleeper_list: SleeperList, + /// Indicates that we have previously pushed a handle onto the + /// SleeperList but have not yet received the Wake message. + /// Being `true` does not necessarily mean that the scheduler is + /// not active since there are multiple event sources that may + /// wake the scheduler. It just prevents the scheduler from pushing + /// multiple handles onto the sleeper list. + priv sleepy: bool, + /// A flag to indicate we've received the shutdown message and should + /// no longer try to go to sleep, but exit instead. + no_sleep: bool, stack_pool: StackPool, /// The event loop used to drive the scheduler and perform I/O event_loop: ~EventLoopObject, @@ -47,6 +63,11 @@ pub struct Scheduler { priv cleanup_job: Option } +pub struct SchedHandle { + priv remote: ~RemoteCallbackObject, + priv queue: MessageQueue +} + pub struct Coroutine { /// The segment of stack on which the task is currently running or, /// if the task is blocked, on which the task will resume execution @@ -58,8 +79,9 @@ pub struct Coroutine { task: ~Task } -pub struct SchedHandle { - priv remote: ~RemoteCallbackObject +pub enum SchedMessage { + Wake, + Shutdown } enum CleanupJob { @@ -81,12 +103,15 @@ pub impl Scheduler { Scheduler { sleeper_list: sleeper_list, + message_queue: MessageQueue::new(), + sleepy: false, + no_sleep: false, event_loop: event_loop, work_queue: work_queue, stack_pool: StackPool::new(), saved_context: Context::empty(), current_task: None, - cleanup_job: None + cleanup_job: None, } } @@ -116,17 +141,51 @@ pub impl Scheduler { return sched; } + fn run_sched_once() { + + let sched = Local::take::(); + if sched.interpret_message_queue() { + // We performed a scheduling action. There may be other work + // to do yet, so let's try again later. + let mut sched = Local::take::(); + sched.event_loop.callback(Scheduler::run_sched_once); + Local::put(sched); + return; + } + + let sched = Local::take::(); + if sched.resume_task_from_queue() { + // We performed a scheduling action. There may be other work + // to do yet, so let's try again later. + let mut sched = Local::take::(); + sched.event_loop.callback(Scheduler::run_sched_once); + Local::put(sched); + return; + } + + // If we got here then there was no work to do. + // Generate a SchedHandle and push it to the sleeper list so + // somebody can wake us up later. + rtdebug!("no work to do"); + let mut sched = Local::take::(); + if !sched.sleepy && !sched.no_sleep { + rtdebug!("sleeping"); + sched.sleepy = true; + let handle = sched.make_handle(); + sched.sleeper_list.push(handle); + } else { + rtdebug!("not sleeping"); + } + Local::put(sched); + } + fn make_handle(&mut self) -> SchedHandle { - let remote = self.event_loop.remote_callback(wake_up); + let remote = self.event_loop.remote_callback(Scheduler::run_sched_once); return SchedHandle { - remote: remote + remote: remote, + queue: self.message_queue.clone() }; - - fn wake_up() { - let sched = Local::take::(); - sched.resume_task_from_queue(); - } } /// Schedule a task to be executed later. @@ -136,17 +195,63 @@ pub impl Scheduler { /// directly. fn enqueue_task(&mut self, task: ~Coroutine) { self.work_queue.push(task); - self.event_loop.callback(resume_task_from_queue); + self.event_loop.callback(Scheduler::run_sched_once); - fn resume_task_from_queue() { - let sched = Local::take::(); - sched.resume_task_from_queue(); + // We've made work available. Notify a sleeping scheduler. + match self.sleeper_list.pop() { + Some(handle) => { + let mut handle = handle; + handle.send(Wake) + } + None => (/* pass */) } } // * Scheduler-context operations - fn resume_task_from_queue(~self) { + fn interpret_message_queue(~self) -> bool { + assert!(!self.in_task_context()); + + rtdebug!("looking for scheduler messages"); + + let mut this = self; + match this.message_queue.pop() { + Some(Wake) => { + rtdebug!("recv Wake message"); + this.sleepy = false; + Local::put(this); + return true; + } + Some(Shutdown) => { + rtdebug!("recv Shutdown message"); + if this.sleepy { + // There may be an outstanding handle on the sleeper list. + // Pop them all to make sure that's not the case. + loop { + match this.sleeper_list.pop() { + Some(handle) => { + let mut handle = handle; + handle.send(Wake); + } + None => (/* pass */) + } + } + } + // No more sleeping. After there are no outstanding event loop + // references we will shut down. + this.no_sleep = true; + this.sleepy = false; + Local::put(this); + return true; + } + None => { + Local::put(this); + return false; + } + } + } + + fn resume_task_from_queue(~self) -> bool { assert!(!self.in_task_context()); rtdebug!("looking in work queue for task to schedule"); @@ -156,10 +261,12 @@ pub impl Scheduler { Some(task) => { rtdebug!("resuming task from work queue"); this.resume_task_immediately(task); + return true; } None => { rtdebug!("no tasks in queue"); Local::put(this); + return false; } } } @@ -363,6 +470,13 @@ pub impl Scheduler { } } +impl SchedHandle { + pub fn send(&mut self, msg: SchedMessage) { + self.queue.push(msg); + self.remote.fire(); + } +} + pub impl Coroutine { fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { Coroutine::with_task(stack_pool, ~Task::new(), start) @@ -621,71 +735,25 @@ mod test { #[test] fn multithreading() { - use clone::Clone; - use iter::Times; - use rt::work_queue::WorkQueue; use rt::comm::*; - use container::Container; + use iter::Times; use vec::OwnedVector; - use rt::rtio::RemoteCallback; - use rt::sleeper_list::SleeperList; + use container::Container; - do run_in_bare_thread { - let sleepers1 = SleeperList::new(); - let work_queue1 = WorkQueue::new(); + do run_in_mt_newsched_task { + let mut ports = ~[]; + for 10.times { + let (port, chan) = oneshot(); + let chan_cell = Cell(chan); + do spawntask_later { + chan_cell.take().send(()); + } + ports.push(port); + } - let sleepers2 = sleepers1.clone(); - let work_queue2 = work_queue1.clone(); - - let loop1 = ~UvEventLoop::new(); - let mut sched1 = ~Scheduler::new(loop1, work_queue1.clone(), sleepers1); - let handle1 = sched1.make_handle(); - let sched1_cell = Cell(sched1); - let handle1_cell = Cell(handle1); - - let loop2 = ~UvEventLoop::new(); - let mut sched2 = ~Scheduler::new(loop2, work_queue2.clone(), sleepers2); - let handle2 = sched2.make_handle(); - let sched2_cell = Cell(sched2); - let handle2_cell = Cell(handle2); - - let _thread1 = do Thread::start { - let mut sched1 = sched1_cell.take(); - sched1.run(); - }; - - let _thread2 = do Thread::start { - let mut sched2 = sched2_cell.take(); - let handle1_cell = Cell(handle1_cell.take()); - let handle2_cell = Cell(handle2_cell.take()); - - let task = ~do Coroutine::new(&mut sched2.stack_pool) { - // Hold handles to keep the schedulers alive - let mut handle1 = handle1_cell.take(); - let mut handle2 = handle2_cell.take(); - - let mut ports = ~[]; - for 10.times { - let (port, chan) = oneshot(); - let chan_cell = Cell(chan); - do spawntask_later { - chan_cell.take().send(()); - } - ports.push(port); - - // Make sure the other scheduler is awake - handle1.remote.fire(); - handle2.remote.fire(); - } - - while !ports.is_empty() { - ports.pop().recv(); - } - }; - - sched2.enqueue_task(task); - sched2.run(); - }; + while !ports.is_empty() { + ports.pop().recv(); + } } } } diff --git a/src/libcore/rt/test.rs b/src/libcore/rt/test.rs index a66e4f09fe72..1bbfe8d473db 100644 --- a/src/libcore/rt/test.rs +++ b/src/libcore/rt/test.rs @@ -13,6 +13,7 @@ use option::{Option, Some, None}; use cell::Cell; use clone::Clone; use container::Container; +use old_iter::MutableIter; use vec::OwnedVector; use result::{Result, Ok, Err}; use unstable::run_in_bare_thread; @@ -29,7 +30,10 @@ pub fn new_test_uv_sched() -> Scheduler { use rt::work_queue::WorkQueue; use rt::sleeper_list::SleeperList; - Scheduler::new(~UvEventLoop::new(), WorkQueue::new(), SleeperList::new()) + let mut sched = Scheduler::new(~UvEventLoop::new(), WorkQueue::new(), SleeperList::new()); + // Don't wait for the Shutdown message + sched.no_sleep = true; + return sched; } /// Creates a new scheduler in a new thread and runs a task in it, @@ -57,6 +61,7 @@ pub fn run_in_newsched_task(f: ~fn()) { /// until the function `f` returns. pub fn run_in_mt_newsched_task(f: ~fn()) { use rt::uv::uvio::UvEventLoop; + use rt::sched::Shutdown; let f_cell = Cell(f); @@ -78,11 +83,15 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { } let f_cell = Cell(f_cell.take()); - let handles = handles; // Freeze + let handles = Cell(handles); let main_task = ~do Coroutine::new(&mut scheds[0].stack_pool) { f_cell.take()(); - // Hold on to handles until the function exits. This keeps the schedulers alive. - let _captured_handles = &handles; + + let mut handles = handles.take(); + // Tell schedulers to exit + for handles.each_mut |handle| { + handle.send(Shutdown); + } }; scheds[0].enqueue_task(main_task); From f343e6172b7132545c72e3e09e6afccc06fdcee7 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 29 May 2013 17:25:29 -0700 Subject: [PATCH 14/55] core::rt: Fix an infinite recursion bug --- src/libcore/rt/comm.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/libcore/rt/comm.rs b/src/libcore/rt/comm.rs index 576a402b7091..d108e20347a0 100644 --- a/src/libcore/rt/comm.rs +++ b/src/libcore/rt/comm.rs @@ -22,6 +22,7 @@ use ops::Drop; use kinds::Owned; use rt::sched::{Scheduler, Coroutine}; use rt::local::Local; +use rt::rtio::EventLoop; use unstable::intrinsics::{atomic_xchg, atomic_load}; use util::Void; use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable}; @@ -172,9 +173,17 @@ impl PortOne { } STATE_ONE => { // Channel is closed. Switch back and check the data. + // NB: We have to drop back into the scheduler event loop here + // instead of switching immediately back or we could end up + // triggering infinite recursion on the scheduler's stack. let task: ~Coroutine = cast::transmute(task_as_state); - let sched = Local::take::(); - sched.resume_task_immediately(task); + let task = Cell(task); + let mut sched = Local::take::(); + do sched.event_loop.callback { + let sched = Local::take::(); + sched.resume_task_immediately(task.take()); + } + Local::put(sched); } _ => util::unreachable() } @@ -614,5 +623,15 @@ mod test { } } } + + #[test] + fn recv_a_lot() { + // Regression test that we don't run out of stack in scheduler context + do run_in_newsched_task { + let (port, chan) = stream(); + for 10000.times { chan.send(()) } + for 10000.times { port.recv() } + } + } } From 134bb0f3eeed69bbf6dc672bbbfbc802f1a018a9 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 29 May 2013 17:52:00 -0700 Subject: [PATCH 15/55] core::rt: Change the signature of context switching methods to avoid infinite recursion --- src/libcore/rt/comm.rs | 4 +-- src/libcore/rt/mod.rs | 7 ++--- src/libcore/rt/sched.rs | 63 +++++++++++++++++++-------------------- src/libcore/rt/test.rs | 32 ++++++-------------- src/libcore/rt/tube.rs | 34 +++++++++------------ src/libcore/rt/uv/uvio.rs | 26 +++++++--------- 6 files changed, 66 insertions(+), 100 deletions(-) diff --git a/src/libcore/rt/comm.rs b/src/libcore/rt/comm.rs index d108e20347a0..8ff3887f779c 100644 --- a/src/libcore/rt/comm.rs +++ b/src/libcore/rt/comm.rs @@ -159,7 +159,7 @@ impl PortOne { // Switch to the scheduler to put the ~Task into the Packet state. let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { + do sched.deschedule_running_task_and_then |sched, task| { unsafe { // Atomically swap the task pointer into the Packet state, issuing // an acquire barrier to prevent reordering of the subsequent read @@ -178,12 +178,10 @@ impl PortOne { // triggering infinite recursion on the scheduler's stack. let task: ~Coroutine = cast::transmute(task_as_state); let task = Cell(task); - let mut sched = Local::take::(); do sched.event_loop.callback { let sched = Local::take::(); sched.resume_task_immediately(task.take()); } - Local::put(sched); } _ => util::unreachable() } diff --git a/src/libcore/rt/mod.rs b/src/libcore/rt/mod.rs index e23ad76a8c61..1113d7abe7dc 100644 --- a/src/libcore/rt/mod.rs +++ b/src/libcore/rt/mod.rs @@ -238,12 +238,9 @@ fn test_context() { let task = ~do Coroutine::new(&mut sched.stack_pool) { assert_eq!(context(), TaskContext); let sched = Local::take::(); - do sched.deschedule_running_task_and_then() |task| { + do sched.deschedule_running_task_and_then() |sched, task| { assert_eq!(context(), SchedulerContext); - let task = Cell(task); - do Local::borrow:: |sched| { - sched.enqueue_task(task.take()); - } + sched.enqueue_task(task); } }; sched.enqueue_task(task); diff --git a/src/libcore/rt/sched.rs b/src/libcore/rt/sched.rs index c6d6bb9f39e5..089c95cd7cd5 100644 --- a/src/libcore/rt/sched.rs +++ b/src/libcore/rt/sched.rs @@ -280,11 +280,9 @@ pub impl Scheduler { rtdebug!("ending running task"); - do self.deschedule_running_task_and_then |dead_task| { + do self.deschedule_running_task_and_then |sched, dead_task| { let dead_task = Cell(dead_task); - do Local::borrow:: |sched| { - dead_task.take().recycle(&mut sched.stack_pool); - } + dead_task.take().recycle(&mut sched.stack_pool); } abort!("control reached end of task"); @@ -293,22 +291,18 @@ pub impl Scheduler { fn schedule_new_task(~self, task: ~Coroutine) { assert!(self.in_task_context()); - do self.switch_running_tasks_and_then(task) |last_task| { + do self.switch_running_tasks_and_then(task) |sched, last_task| { let last_task = Cell(last_task); - do Local::borrow:: |sched| { - sched.enqueue_task(last_task.take()); - } + sched.enqueue_task(last_task.take()); } } fn schedule_task(~self, task: ~Coroutine) { assert!(self.in_task_context()); - do self.switch_running_tasks_and_then(task) |last_task| { + do self.switch_running_tasks_and_then(task) |sched, last_task| { let last_task = Cell(last_task); - do Local::borrow:: |sched| { - sched.enqueue_task(last_task.take()); - } + sched.enqueue_task(last_task.take()); } } @@ -352,7 +346,11 @@ pub impl Scheduler { /// The closure here is a *stack* closure that lives in the /// running task. It gets transmuted to the scheduler's lifetime /// and called while the task is blocked. - fn deschedule_running_task_and_then(~self, f: &fn(~Coroutine)) { + /// + /// This passes a Scheduler pointer to the fn after the context switch + /// in order to prevent that fn from performing further scheduling operations. + /// Doing further scheduling could easily result in infinite recursion. + fn deschedule_running_task_and_then(~self, f: &fn(&mut Scheduler, ~Coroutine)) { let mut this = self; assert!(this.in_task_context()); @@ -360,7 +358,8 @@ pub impl Scheduler { unsafe { let blocked_task = this.current_task.swap_unwrap(); - let f_fake_region = transmute::<&fn(~Coroutine), &fn(~Coroutine)>(f); + let f_fake_region = transmute::<&fn(&mut Scheduler, ~Coroutine), + &fn(&mut Scheduler, ~Coroutine)>(f); let f_opaque = ClosureConverter::from_fn(f_fake_region); this.enqueue_cleanup_job(GiveTask(blocked_task, f_opaque)); } @@ -382,14 +381,18 @@ pub impl Scheduler { /// Switch directly to another task, without going through the scheduler. /// You would want to think hard about doing this, e.g. if there are /// pending I/O events it would be a bad idea. - fn switch_running_tasks_and_then(~self, next_task: ~Coroutine, f: &fn(~Coroutine)) { + fn switch_running_tasks_and_then(~self, next_task: ~Coroutine, + f: &fn(&mut Scheduler, ~Coroutine)) { let mut this = self; assert!(this.in_task_context()); rtdebug!("switching tasks"); let old_running_task = this.current_task.swap_unwrap(); - let f_fake_region = unsafe { transmute::<&fn(~Coroutine), &fn(~Coroutine)>(f) }; + let f_fake_region = unsafe { + transmute::<&fn(&mut Scheduler, ~Coroutine), + &fn(&mut Scheduler, ~Coroutine)>(f) + }; let f_opaque = ClosureConverter::from_fn(f_fake_region); this.enqueue_cleanup_job(GiveTask(old_running_task, f_opaque)); this.current_task = Some(next_task); @@ -426,7 +429,7 @@ pub impl Scheduler { let cleanup_job = self.cleanup_job.swap_unwrap(); match cleanup_job { DoNothing => { } - GiveTask(task, f) => (f.to_fn())(task) + GiveTask(task, f) => (f.to_fn())(self, task) } } @@ -535,12 +538,12 @@ pub impl Coroutine { // complaining type UnsafeTaskReceiver = sys::Closure; trait ClosureConverter { - fn from_fn(&fn(~Coroutine)) -> Self; - fn to_fn(self) -> &fn(~Coroutine); + fn from_fn(&fn(&mut Scheduler, ~Coroutine)) -> Self; + fn to_fn(self) -> &fn(&mut Scheduler, ~Coroutine); } impl ClosureConverter for UnsafeTaskReceiver { - fn from_fn(f: &fn(~Coroutine)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } - fn to_fn(self) -> &fn(~Coroutine) { unsafe { transmute(self) } } + fn from_fn(f: &fn(&mut Scheduler, ~Coroutine)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } + fn to_fn(self) -> &fn(&mut Scheduler, ~Coroutine) { unsafe { transmute(self) } } } #[cfg(test)] @@ -604,11 +607,9 @@ mod test { unsafe { *count_ptr = *count_ptr + 1; } }; // Context switch directly to the new task - do sched.switch_running_tasks_and_then(task2) |task1| { + do sched.switch_running_tasks_and_then(task2) |sched, task1| { let task1 = Cell(task1); - do Local::borrow:: |sched| { - sched.enqueue_task(task1.take()); - } + sched.enqueue_task(task1.take()); } unsafe { *count_ptr = *count_ptr + 1; } }; @@ -658,12 +659,10 @@ mod test { let task = ~do Coroutine::new(&mut sched.stack_pool) { let sched = Local::take::(); assert!(sched.in_task_context()); - do sched.deschedule_running_task_and_then() |task| { + do sched.deschedule_running_task_and_then() |sched, task| { let task = Cell(task); - do Local::borrow:: |sched| { - assert!(!sched.in_task_context()); - sched.enqueue_task(task.take()); - } + assert!(!sched.in_task_context()); + sched.enqueue_task(task.take()); } }; sched.enqueue_task(task); @@ -680,8 +679,7 @@ mod test { do run_in_newsched_task { do spawn { let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { - let mut sched = Local::take::(); + do sched.deschedule_running_task_and_then |sched, task| { let task = Cell(task); do sched.event_loop.callback_ms(10) { rtdebug!("in callback"); @@ -689,7 +687,6 @@ mod test { sched.enqueue_task(task.take()); Local::put(sched); } - Local::put(sched); } } } diff --git a/src/libcore/rt/test.rs b/src/libcore/rt/test.rs index 1bbfe8d473db..16b0aef5e266 100644 --- a/src/libcore/rt/test.rs +++ b/src/libcore/rt/test.rs @@ -122,11 +122,7 @@ pub fn spawntask(f: ~fn()) { let task = ~Coroutine::with_task(&mut sched.stack_pool, ~Task::without_unwinding(), f); - do sched.switch_running_tasks_and_then(task) |task| { - let task = Cell(task); - let sched = Local::take::(); - sched.schedule_new_task(task.take()); - } + sched.schedule_new_task(task); } /// Create a new task and run it right now. Aborts on failure @@ -137,11 +133,8 @@ pub fn spawntask_immediately(f: ~fn()) { let task = ~Coroutine::with_task(&mut sched.stack_pool, ~Task::without_unwinding(), f); - do sched.switch_running_tasks_and_then(task) |task| { - let task = Cell(task); - do Local::borrow:: |sched| { - sched.enqueue_task(task.take()); - } + do sched.switch_running_tasks_and_then(task) |sched, task| { + sched.enqueue_task(task); } } @@ -172,11 +165,8 @@ pub fn spawntask_random(f: ~fn()) { f); if run_now { - do sched.switch_running_tasks_and_then(task) |task| { - let task = Cell(task); - do Local::borrow:: |sched| { - sched.enqueue_task(task.take()); - } + do sched.switch_running_tasks_and_then(task) |sched, task| { + sched.enqueue_task(task); } } else { sched.enqueue_task(task); @@ -199,10 +189,9 @@ pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { // Switch to the scheduler let f = Cell(Cell(f)); let sched = Local::take::(); - do sched.deschedule_running_task_and_then() |old_task| { + do sched.deschedule_running_task_and_then() |sched, old_task| { let old_task = Cell(old_task); let f = f.take(); - let mut sched = Local::take::(); let new_task = ~do Coroutine::new(&mut sched.stack_pool) { do (|| { (f.take())() @@ -210,16 +199,13 @@ pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { // Check for failure then resume the parent task unsafe { *failed_ptr = task::failing(); } let sched = Local::take::(); - do sched.switch_running_tasks_and_then(old_task.take()) |new_task| { - let new_task = Cell(new_task); - do Local::borrow:: |sched| { - sched.enqueue_task(new_task.take()); - } + do sched.switch_running_tasks_and_then(old_task.take()) |sched, new_task| { + sched.enqueue_task(new_task); } } }; - sched.resume_task_immediately(new_task); + sched.enqueue_task(new_task); } if !failed { Ok(()) } else { Err(()) } diff --git a/src/libcore/rt/tube.rs b/src/libcore/rt/tube.rs index b2f475a69660..4482a92d916a 100644 --- a/src/libcore/rt/tube.rs +++ b/src/libcore/rt/tube.rs @@ -72,7 +72,7 @@ impl Tube { assert!(self.p.refcount() > 1); // There better be somebody to wake us up assert!((*state).blocked_task.is_none()); let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { + do sched.deschedule_running_task_and_then |_, task| { (*state).blocked_task = Some(task); } rtdebug!("waking after tube recv"); @@ -107,11 +107,10 @@ mod test { let tube_clone = tube.clone(); let tube_clone_cell = Cell(tube_clone); let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { + do sched.deschedule_running_task_and_then |sched, task| { let mut tube_clone = tube_clone_cell.take(); tube_clone.send(1); - let sched = Local::take::(); - sched.resume_task_immediately(task); + sched.enqueue_task(task); } assert!(tube.recv() == 1); @@ -123,21 +122,17 @@ mod test { do run_in_newsched_task { let mut tube: Tube = Tube::new(); let tube_clone = tube.clone(); - let tube_clone = Cell(Cell(Cell(tube_clone))); + let tube_clone = Cell(tube_clone); let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { - let tube_clone = tube_clone.take(); - do Local::borrow:: |sched| { - let tube_clone = tube_clone.take(); - do sched.event_loop.callback { - let mut tube_clone = tube_clone.take(); - // The task should be blocked on this now and - // sending will wake it up. - tube_clone.send(1); - } + do sched.deschedule_running_task_and_then |sched, task| { + let tube_clone = Cell(tube_clone.take()); + do sched.event_loop.callback { + let mut tube_clone = tube_clone.take(); + // The task should be blocked on this now and + // sending will wake it up. + tube_clone.send(1); } - let sched = Local::take::(); - sched.resume_task_immediately(task); + sched.enqueue_task(task); } assert!(tube.recv() == 1); @@ -153,7 +148,7 @@ mod test { let tube_clone = tube.clone(); let tube_clone = Cell(tube_clone); let sched = Local::take::(); - do sched.deschedule_running_task_and_then |task| { + do sched.deschedule_running_task_and_then |sched, task| { callback_send(tube_clone.take(), 0); fn callback_send(tube: Tube, i: int) { @@ -172,8 +167,7 @@ mod test { } } - let sched = Local::take::(); - sched.resume_task_immediately(task); + sched.enqueue_task(task); } for int::range(0, MAX) |i| { diff --git a/src/libcore/rt/uv/uvio.rs b/src/libcore/rt/uv/uvio.rs index e25b6140abbf..1ee6504d11fc 100644 --- a/src/libcore/rt/uv/uvio.rs +++ b/src/libcore/rt/uv/uvio.rs @@ -205,12 +205,10 @@ impl IoFactory for UvIoFactory { assert!(scheduler.in_task_context()); // Block this task and take ownership, switch to scheduler context - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |sched, task| { rtdebug!("connect: entered scheduler context"); - do Local::borrow:: |scheduler| { - assert!(!scheduler.in_task_context()); - } + assert!(!sched.in_task_context()); let mut tcp_watcher = TcpWatcher::new(self.uv_loop()); let task_cell = Cell(task); @@ -250,7 +248,7 @@ impl IoFactory for UvIoFactory { Ok(_) => Ok(~UvTcpListener::new(watcher)), Err(uverr) => { let scheduler = Local::take::(); - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |_, task| { let task_cell = Cell(task); do watcher.as_stream().close { let scheduler = Local::take::(); @@ -286,7 +284,7 @@ impl Drop for UvTcpListener { fn finalize(&self) { let watcher = self.watcher(); let scheduler = Local::take::(); - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |_, task| { let task_cell = Cell(task); do watcher.as_stream().close { let scheduler = Local::take::(); @@ -348,7 +346,7 @@ impl Drop for UvTcpStream { rtdebug!("closing tcp stream"); let watcher = self.watcher(); let scheduler = Local::take::(); - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |_, task| { let task_cell = Cell(task); do watcher.close { let scheduler = Local::take::(); @@ -367,11 +365,9 @@ impl RtioTcpStream for UvTcpStream { assert!(scheduler.in_task_context()); let watcher = self.watcher(); let buf_ptr: *&mut [u8] = &buf; - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |sched, task| { rtdebug!("read: entered scheduler context"); - do Local::borrow:: |scheduler| { - assert!(!scheduler.in_task_context()); - } + assert!(!sched.in_task_context()); let mut watcher = watcher; let task_cell = Cell(task); // XXX: We shouldn't reallocate these callbacks every @@ -413,7 +409,7 @@ impl RtioTcpStream for UvTcpStream { assert!(scheduler.in_task_context()); let watcher = self.watcher(); let buf_ptr: *&[u8] = &buf; - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |_, task| { let mut watcher = watcher; let task_cell = Cell(task); let buf = unsafe { slice_to_uv_buf(*buf_ptr) }; @@ -507,11 +503,9 @@ fn test_read_and_block() { // Yield to the other task in hopes that it // will trigger a read callback while we are // not ready for it - do scheduler.deschedule_running_task_and_then |task| { + do scheduler.deschedule_running_task_and_then |sched, task| { let task = Cell(task); - do Local::borrow:: |scheduler| { - scheduler.enqueue_task(task.take()); - } + sched.enqueue_task(task.take()); } } From ca2eebd5dd8ceea1da77b6a6f4fb8c68462a400b Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 29 May 2013 21:03:21 -0700 Subject: [PATCH 16/55] core::rt: Add some notes about optimizations --- src/libstd/rt/sched.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 089c95cd7cd5..75b530664411 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -29,6 +29,9 @@ use rt::rtio::{IoFactoryObject, RemoteCallback}; /// on a single thread. When the scheduler is running it is owned by /// thread local storage and the running task is owned by the /// scheduler. +/// +/// XXX: This creates too many callbacks to run_sched_once, resulting +/// in too much allocation and too many events. pub struct Scheduler { /// A queue of available work. Under a work-stealing policy there /// is one per Scheduler. @@ -143,6 +146,10 @@ pub impl Scheduler { fn run_sched_once() { + // First, check the message queue for instructions. + // XXX: perf. Check for messages without atomics. + // It's ok if we miss messages occasionally, as long as + // we sync and check again before sleeping. let sched = Local::take::(); if sched.interpret_message_queue() { // We performed a scheduling action. There may be other work @@ -153,6 +160,7 @@ pub impl Scheduler { return; } + // Now, look in the work queue for tasks to run let sched = Local::take::(); if sched.resume_task_from_queue() { // We performed a scheduling action. There may be other work @@ -198,6 +206,12 @@ pub impl Scheduler { self.event_loop.callback(Scheduler::run_sched_once); // We've made work available. Notify a sleeping scheduler. + // XXX: perf. Check for a sleeper without synchronizing memory. + // It's not critical that we always find it. + // XXX: perf. If there's a sleeper then we might as well just send + // it the task directly instead of pushing it to the + // queue. That is essentially the intent here and it is less + // work. match self.sleeper_list.pop() { Some(handle) => { let mut handle = handle; From 8eb358bb00f161f9e289de6cad8cfecc4c6eb681 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 29 May 2013 22:38:15 -0700 Subject: [PATCH 17/55] core::rt: Begin recording scheduler metrics --- src/libstd/rt/comm.rs | 23 ++++++++--- src/libstd/rt/metrics.rs | 88 ++++++++++++++++++++++++++++++++++++++++ src/libstd/rt/mod.rs | 2 + src/libstd/rt/sched.rs | 18 +++++++- 4 files changed, 123 insertions(+), 8 deletions(-) create mode 100644 src/libstd/rt/metrics.rs diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index ebfa9e263ef8..19fb809d4378 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -119,8 +119,16 @@ impl ChanOne { match oldstate { STATE_BOTH => { // Port is not waiting yet. Nothing to do + do Local::borrow:: |sched| { + rtdebug!("non-rendezvous send"); + sched.metrics.non_rendezvous_sends += 1; + } } STATE_ONE => { + do Local::borrow:: |sched| { + rtdebug!("rendezvous send"); + sched.metrics.rendezvous_sends += 1; + } // Port has closed. Need to clean up. let _packet: ~Packet = cast::transmute(this.inner.void_packet); recvr_active = false; @@ -128,7 +136,9 @@ impl ChanOne { task_as_state => { // Port is blocked. Wake it up. let recvr: ~Coroutine = cast::transmute(task_as_state); - let sched = Local::take::(); + let mut sched = Local::take::(); + rtdebug!("rendezvous send"); + sched.metrics.rendezvous_sends += 1; sched.schedule_task(recvr); } } @@ -170,18 +180,19 @@ impl PortOne { match oldstate { STATE_BOTH => { // Data has not been sent. Now we're blocked. + rtdebug!("non-rendezvous recv"); + sched.metrics.non_rendezvous_recvs += 1; } STATE_ONE => { + rtdebug!("rendezvous recv"); + sched.metrics.rendezvous_recvs += 1; + // Channel is closed. Switch back and check the data. // NB: We have to drop back into the scheduler event loop here // instead of switching immediately back or we could end up // triggering infinite recursion on the scheduler's stack. let task: ~Coroutine = cast::transmute(task_as_state); - let task = Cell(task); - do sched.event_loop.callback { - let sched = Local::take::(); - sched.resume_task_immediately(task.take()); - } + sched.enqueue_task(task); } _ => util::unreachable() } diff --git a/src/libstd/rt/metrics.rs b/src/libstd/rt/metrics.rs new file mode 100644 index 000000000000..70e347fdfb6a --- /dev/null +++ b/src/libstd/rt/metrics.rs @@ -0,0 +1,88 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use to_str::ToStr; + +pub struct SchedMetrics { + // The number of times executing `run_sched_once`. + turns: uint, + // The number of turns that received a message. + messages_received: uint, + // The number of turns that ran a task from the queue. + tasks_resumed_from_queue: uint, + // The number of turns that found no work to perform. + wasted_turns: uint, + // The number of times the scheduler went to sleep. + sleepy_times: uint, + // Context switches from the scheduler into a task. + context_switches_sched_to_task: uint, + // Context switches from a task into the scheduler. + context_switches_task_to_sched: uint, + // Context switches from a task to a task. + context_switches_task_to_task: uint, + // Message sends that unblock the receiver + rendezvous_sends: uint, + // Message sends that do not unblock the receiver + non_rendezvous_sends: uint, + // Message receives that do not block the receiver + rendezvous_recvs: uint, + // Message receives that block the receiver + non_rendezvous_recvs: uint +} + +impl SchedMetrics { + pub fn new() -> SchedMetrics { + SchedMetrics { + turns: 0, + messages_received: 0, + tasks_resumed_from_queue: 0, + wasted_turns: 0, + sleepy_times: 0, + context_switches_sched_to_task: 0, + context_switches_task_to_sched: 0, + context_switches_task_to_task: 0, + rendezvous_sends: 0, + non_rendezvous_sends: 0, + rendezvous_recvs: 0, + non_rendezvous_recvs: 0 + } + } +} + +impl ToStr for SchedMetrics { + fn to_str(&self) -> ~str { + fmt!("turns: %u\n\ + messages_received: %u\n\ + tasks_resumed_from_queue: %u\n\ + wasted_turns: %u\n\ + sleepy_times: %u\n\ + context_switches_sched_to_task: %u\n\ + context_switches_task_to_sched: %u\n\ + context_switches_task_to_task: %u\n\ + rendezvous_sends: %u\n\ + non_rendezvous_sends: %u\n\ + rendezvous_recvs: %u\n\ + non_rendezvous_recvs: %u\n\ + ", + self.turns, + self.messages_received, + self.tasks_resumed_from_queue, + self.wasted_turns, + self.sleepy_times, + self.context_switches_sched_to_task, + self.context_switches_task_to_sched, + self.context_switches_task_to_task, + self.rendezvous_sends, + self.non_rendezvous_sends, + self.rendezvous_recvs, + self.non_rendezvous_recvs + ) + } +} \ No newline at end of file diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 1113d7abe7dc..23dc75780022 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -130,6 +130,8 @@ pub mod local_ptr; /// Bindings to pthread/windows thread-local storage. pub mod thread_local_storage; +pub mod metrics; + /// Set up a default runtime configuration, given compiler-supplied arguments. /// diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 75b530664411..b5b8bb732e7f 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -13,6 +13,7 @@ use sys; use cast::transmute; use cell::Cell; use clone::Clone; +use to_str::ToStr; use super::sleeper_list::SleeperList; use super::work_queue::WorkQueue; @@ -24,6 +25,7 @@ use super::message_queue::MessageQueue; use rt::local_ptr; use rt::local::Local; use rt::rtio::{IoFactoryObject, RemoteCallback}; +use rt::metrics::SchedMetrics; /// The Scheduler is responsible for coordinating execution of Coroutines /// on a single thread. When the scheduler is running it is owned by @@ -63,7 +65,8 @@ pub struct Scheduler { current_task: Option<~Coroutine>, /// An action performed after a context switch on behalf of the /// code running before the context switch - priv cleanup_job: Option + priv cleanup_job: Option, + metrics: SchedMetrics } pub struct SchedHandle { @@ -115,6 +118,7 @@ pub impl Scheduler { saved_context: Context::empty(), current_task: None, cleanup_job: None, + metrics: SchedMetrics::new() } } @@ -141,20 +145,24 @@ pub impl Scheduler { let sched = Local::take::(); assert!(sched.work_queue.is_empty()); + rtdebug!("scheduler metrics: %s\n", sched.metrics.to_str()); return sched; } fn run_sched_once() { + let mut sched = Local::take::(); + sched.metrics.turns += 1; + // First, check the message queue for instructions. // XXX: perf. Check for messages without atomics. // It's ok if we miss messages occasionally, as long as // we sync and check again before sleeping. - let sched = Local::take::(); if sched.interpret_message_queue() { // We performed a scheduling action. There may be other work // to do yet, so let's try again later. let mut sched = Local::take::(); + sched.metrics.messages_received += 1; sched.event_loop.callback(Scheduler::run_sched_once); Local::put(sched); return; @@ -166,6 +174,7 @@ pub impl Scheduler { // We performed a scheduling action. There may be other work // to do yet, so let's try again later. let mut sched = Local::take::(); + sched.metrics.tasks_resumed_from_queue += 1; sched.event_loop.callback(Scheduler::run_sched_once); Local::put(sched); return; @@ -176,8 +185,10 @@ pub impl Scheduler { // somebody can wake us up later. rtdebug!("no work to do"); let mut sched = Local::take::(); + sched.metrics.wasted_turns += 1; if !sched.sleepy && !sched.no_sleep { rtdebug!("sleeping"); + sched.metrics.sleepy_times += 1; sched.sleepy = true; let handle = sched.make_handle(); sched.sleeper_list.push(handle); @@ -327,6 +338,7 @@ pub impl Scheduler { assert!(!this.in_task_context()); rtdebug!("scheduling a task"); + this.metrics.context_switches_sched_to_task += 1; // Store the task in the scheduler so it can be grabbed later this.current_task = Some(task); @@ -369,6 +381,7 @@ pub impl Scheduler { assert!(this.in_task_context()); rtdebug!("blocking task"); + this.metrics.context_switches_task_to_sched += 1; unsafe { let blocked_task = this.current_task.swap_unwrap(); @@ -401,6 +414,7 @@ pub impl Scheduler { assert!(this.in_task_context()); rtdebug!("switching tasks"); + this.metrics.context_switches_task_to_task += 1; let old_running_task = this.current_task.swap_unwrap(); let f_fake_region = unsafe { From 053b38e7e1cba8f7bb649a5fc8d82b0448d33c55 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 30 May 2013 00:18:49 -0700 Subject: [PATCH 18/55] core::rt: Fix two multithreading bugs and add a threadring test This properly distributes the load now --- src/libstd/rt/sched.rs | 66 +++++++++++++++++++++++++++++++++++++++++- src/libstd/rt/test.rs | 2 +- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index b5b8bb732e7f..a57a87ffba77 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -131,6 +131,11 @@ pub impl Scheduler { let mut self_sched = self; + // Always run through the scheduler loop at least once so that + // we enter the sleep state and can then be woken up by other + // schedulers. + self_sched.event_loop.callback(Scheduler::run_sched_once); + unsafe { let event_loop: *mut ~EventLoopObject = { let event_loop: *mut ~EventLoopObject = &mut self_sched.event_loop; @@ -258,7 +263,7 @@ pub impl Scheduler { let mut handle = handle; handle.send(Wake); } - None => (/* pass */) + None => break } } } @@ -781,4 +786,63 @@ mod test { } } } + + #[test] + fn thread_ring() { + use rt::comm::*; + use iter::Times; + use vec::OwnedVector; + use container::Container; + use comm::{GenericPort, GenericChan}; + + do run_in_mt_newsched_task { + let (end_port, end_chan) = oneshot(); + + let n_tasks = 10; + let token = 2000; + + let mut (p, ch1) = stream(); + ch1.send((token, end_chan)); + let mut i = 2; + while i <= n_tasks { + let (next_p, ch) = stream(); + let imm_i = i; + let imm_p = p; + do spawntask_random { + roundtrip(imm_i, n_tasks, &imm_p, &ch); + }; + p = next_p; + i += 1; + } + let imm_p = p; + let imm_ch = ch1; + do spawntask_random { + roundtrip(1, n_tasks, &imm_p, &imm_ch); + } + + end_port.recv(); + } + + fn roundtrip(id: int, n_tasks: int, + p: &Port<(int, ChanOne<()>)>, ch: &Chan<(int, ChanOne<()>)>) { + while (true) { + match p.recv() { + (1, end_chan) => { + debug!("%d\n", id); + end_chan.send(()); + return; + } + (token, end_chan) => { + debug!("thread: %d got token: %d", id, token); + ch.send((token - 1, end_chan)); + if token <= n_tasks { + return; + } + } + } + } + } + + } + } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index 16b0aef5e266..e05e2004e0b2 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -66,7 +66,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let f_cell = Cell(f); do run_in_bare_thread { - static N: uint = 2; + static N: uint = 4; let sleepers = SleeperList::new(); let work_queue = WorkQueue::new(); From ea633b42aeadf807a10036a87bf2903123250152 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 30 May 2013 13:20:17 -0700 Subject: [PATCH 19/55] core::rt: deny(unused_imports, unused_mut, unused_variable) --- src/libstd/rt/comm.rs | 1 - src/libstd/rt/local.rs | 1 - src/libstd/rt/mod.rs | 5 +++-- src/libstd/rt/sched.rs | 11 +++-------- src/libstd/rt/sleeper_list.rs | 2 +- src/libstd/rt/test.rs | 8 +++----- src/libstd/rt/uv/uvio.rs | 5 +---- 7 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 19fb809d4378..26d02fb6640a 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -22,7 +22,6 @@ use ops::Drop; use kinds::Owned; use rt::sched::{Scheduler, Coroutine}; use rt::local::Local; -use rt::rtio::EventLoop; use unstable::intrinsics::{atomic_xchg, atomic_load}; use util::Void; use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable}; diff --git a/src/libstd/rt/local.rs b/src/libstd/rt/local.rs index ffff54f00bbe..e6988c538881 100644 --- a/src/libstd/rt/local.rs +++ b/src/libstd/rt/local.rs @@ -87,7 +87,6 @@ impl Local for IoFactoryObject { mod test { use rt::test::*; use rt::sched::Scheduler; - use rt::uv::uvio::UvEventLoop; use super::*; #[test] diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 23dc75780022..caf3e15e535a 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -55,6 +55,9 @@ Several modules in `core` are clients of `rt`: */ #[doc(hidden)]; +#[deny(unused_imports)]; +#[deny(unused_mut)]; +#[deny(unused_variable)]; use ptr::Ptr; @@ -228,8 +231,6 @@ pub fn context() -> RuntimeContext { fn test_context() { use unstable::run_in_bare_thread; use self::sched::{Scheduler, Coroutine}; - use rt::uv::uvio::UvEventLoop; - use cell::Cell; use rt::local::Local; use rt::test::new_test_uv_sched; diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index a57a87ffba77..b0080a010140 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -13,7 +13,6 @@ use sys; use cast::transmute; use cell::Cell; use clone::Clone; -use to_str::ToStr; use super::sleeper_list::SleeperList; use super::work_queue::WorkQueue; @@ -24,7 +23,7 @@ use super::task::Task; use super::message_queue::MessageQueue; use rt::local_ptr; use rt::local::Local; -use rt::rtio::{IoFactoryObject, RemoteCallback}; +use rt::rtio::RemoteCallback; use rt::metrics::SchedMetrics; /// The Scheduler is responsible for coordinating execution of Coroutines @@ -583,7 +582,6 @@ impl ClosureConverter for UnsafeTaskReceiver { mod test { use int; use cell::Cell; - use rt::uv::uvio::UvEventLoop; use unstable::run_in_bare_thread; use task::spawn; use rt::local::Local; @@ -751,13 +749,13 @@ mod test { let sched1_cell = Cell(sched1); let _thread1 = do Thread::start { - let mut sched1 = sched1_cell.take(); + let sched1 = sched1_cell.take(); sched1.run(); }; let sched2_cell = Cell(sched2); let _thread2 = do Thread::start { - let mut sched2 = sched2_cell.take(); + let sched2 = sched2_cell.take(); sched2.run(); }; } @@ -790,9 +788,6 @@ mod test { #[test] fn thread_ring() { use rt::comm::*; - use iter::Times; - use vec::OwnedVector; - use container::Container; use comm::{GenericPort, GenericChan}; do run_in_mt_newsched_task { diff --git a/src/libstd/rt/sleeper_list.rs b/src/libstd/rt/sleeper_list.rs index dfcac8eb088f..e2873e78d805 100644 --- a/src/libstd/rt/sleeper_list.rs +++ b/src/libstd/rt/sleeper_list.rs @@ -16,7 +16,7 @@ use vec::OwnedVector; use option::{Option, Some, None}; use cell::Cell; use unstable::sync::{Exclusive, exclusive}; -use rt::sched::{Scheduler, SchedHandle}; +use rt::sched::SchedHandle; use clone::Clone; pub struct SleeperList { diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index e05e2004e0b2..907d289fb074 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -9,7 +9,7 @@ // except according to those terms. use uint; -use option::{Option, Some, None}; +use option::{Some, None}; use cell::Cell; use clone::Clone; use container::Container; @@ -42,7 +42,6 @@ pub fn new_test_uv_sched() -> Scheduler { pub fn run_in_newsched_task(f: ~fn()) { use super::sched::*; use unstable::run_in_bare_thread; - use rt::uv::uvio::UvEventLoop; let f = Cell(f); @@ -74,7 +73,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let mut handles = ~[]; let mut scheds = ~[]; - for uint::range(0, N) |i| { + for uint::range(0, N) |_| { let loop_ = ~UvEventLoop::new(); let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); let handle = sched.make_handle(); @@ -102,7 +101,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let sched = scheds.pop(); let sched_cell = Cell(sched); let thread = do Thread::start { - let mut sched = sched_cell.take(); + let sched = sched_cell.take(); sched.run(); }; @@ -214,7 +213,6 @@ pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { // Spawn a new task in a new scheduler and return a thread handle. pub fn spawntask_thread(f: ~fn()) -> Thread { use rt::sched::*; - use rt::uv::uvio::UvEventLoop; let f = Cell(f); let thread = do Thread::start { diff --git a/src/libstd/rt/uv/uvio.rs b/src/libstd/rt/uv/uvio.rs index 1ee6504d11fc..0d9530239a3d 100644 --- a/src/libstd/rt/uv/uvio.rs +++ b/src/libstd/rt/uv/uvio.rs @@ -24,9 +24,7 @@ use rt::sched::Scheduler; use rt::io::{standard_error, OtherIoError}; use rt::tube::Tube; use rt::local::Local; -use rt::work_queue::WorkQueue; use unstable::sync::{UnsafeAtomicRcBox, AtomicInt}; -use unstable::intrinsics; #[cfg(test)] use container::Container; #[cfg(test)] use uint; @@ -140,7 +138,7 @@ impl RemoteCallback for UvRemoteCallback { impl Drop for UvRemoteCallback { fn finalize(&self) { unsafe { - let mut this: &mut UvRemoteCallback = cast::transmute_mut(self); + let this: &mut UvRemoteCallback = cast::transmute_mut(self); let exit_flag_ptr = this.exit_flag.get(); (*exit_flag_ptr).store(1); this.async.send(); @@ -150,7 +148,6 @@ impl Drop for UvRemoteCallback { #[cfg(test)] mod test_remote { - use super::*; use cell; use cell::Cell; use rt::test::*; From e2bedb1b868a634885df9f8a277bec1915c98fc2 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sat, 1 Jun 2013 02:11:38 -0700 Subject: [PATCH 20/55] core: Make atomic methods public --- src/libstd/unstable/atomics.rs | 62 +++++++++++++++++----------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/src/libstd/unstable/atomics.rs b/src/libstd/unstable/atomics.rs index ab2b5d8ea2b3..58d0c01f990d 100644 --- a/src/libstd/unstable/atomics.rs +++ b/src/libstd/unstable/atomics.rs @@ -75,7 +75,7 @@ pub enum Ordering { impl AtomicFlag { - fn new() -> AtomicFlag { + pub fn new() -> AtomicFlag { AtomicFlag { v: 0 } } @@ -83,7 +83,7 @@ impl AtomicFlag { * Clears the atomic flag */ #[inline(always)] - fn clear(&mut self, order: Ordering) { + pub fn clear(&mut self, order: Ordering) { unsafe {atomic_store(&mut self.v, 0, order)} } @@ -92,37 +92,37 @@ impl AtomicFlag { * flag. */ #[inline(always)] - fn test_and_set(&mut self, order: Ordering) -> bool { + pub fn test_and_set(&mut self, order: Ordering) -> bool { unsafe {atomic_compare_and_swap(&mut self.v, 0, 1, order) > 0} } } impl AtomicBool { - fn new(v: bool) -> AtomicBool { + pub fn new(v: bool) -> AtomicBool { AtomicBool { v: if v { 1 } else { 0 } } } #[inline(always)] - fn load(&self, order: Ordering) -> bool { + pub fn load(&self, order: Ordering) -> bool { unsafe { atomic_load(&self.v, order) > 0 } } #[inline(always)] - fn store(&mut self, val: bool, order: Ordering) { + pub fn store(&mut self, val: bool, order: Ordering) { let val = if val { 1 } else { 0 }; unsafe { atomic_store(&mut self.v, val, order); } } #[inline(always)] - fn swap(&mut self, val: bool, order: Ordering) -> bool { + pub fn swap(&mut self, val: bool, order: Ordering) -> bool { let val = if val { 1 } else { 0 }; unsafe { atomic_swap(&mut self.v, val, order) > 0} } #[inline(always)] - fn compare_and_swap(&mut self, old: bool, new: bool, order: Ordering) -> bool { + pub fn compare_and_swap(&mut self, old: bool, new: bool, order: Ordering) -> bool { let old = if old { 1 } else { 0 }; let new = if new { 1 } else { 0 }; @@ -131,105 +131,105 @@ impl AtomicBool { } impl AtomicInt { - fn new(v: int) -> AtomicInt { + pub fn new(v: int) -> AtomicInt { AtomicInt { v:v } } #[inline(always)] - fn load(&self, order: Ordering) -> int { + pub fn load(&self, order: Ordering) -> int { unsafe { atomic_load(&self.v, order) } } #[inline(always)] - fn store(&mut self, val: int, order: Ordering) { + pub fn store(&mut self, val: int, order: Ordering) { unsafe { atomic_store(&mut self.v, val, order); } } #[inline(always)] - fn swap(&mut self, val: int, order: Ordering) -> int { + pub fn swap(&mut self, val: int, order: Ordering) -> int { unsafe { atomic_swap(&mut self.v, val, order) } } #[inline(always)] - fn compare_and_swap(&mut self, old: int, new: int, order: Ordering) -> int { + pub fn compare_and_swap(&mut self, old: int, new: int, order: Ordering) -> int { unsafe { atomic_compare_and_swap(&mut self.v, old, new, order) } } #[inline(always)] - fn fetch_add(&mut self, val: int, order: Ordering) -> int { + pub fn fetch_add(&mut self, val: int, order: Ordering) -> int { unsafe { atomic_add(&mut self.v, val, order) } } #[inline(always)] - fn fetch_sub(&mut self, val: int, order: Ordering) -> int { + pub fn fetch_sub(&mut self, val: int, order: Ordering) -> int { unsafe { atomic_sub(&mut self.v, val, order) } } } impl AtomicUint { - fn new(v: uint) -> AtomicUint { + pub fn new(v: uint) -> AtomicUint { AtomicUint { v:v } } #[inline(always)] - fn load(&self, order: Ordering) -> uint { + pub fn load(&self, order: Ordering) -> uint { unsafe { atomic_load(&self.v, order) } } #[inline(always)] - fn store(&mut self, val: uint, order: Ordering) { + pub fn store(&mut self, val: uint, order: Ordering) { unsafe { atomic_store(&mut self.v, val, order); } } #[inline(always)] - fn swap(&mut self, val: uint, order: Ordering) -> uint { + pub fn swap(&mut self, val: uint, order: Ordering) -> uint { unsafe { atomic_swap(&mut self.v, val, order) } } #[inline(always)] - fn compare_and_swap(&mut self, old: uint, new: uint, order: Ordering) -> uint { + pub fn compare_and_swap(&mut self, old: uint, new: uint, order: Ordering) -> uint { unsafe { atomic_compare_and_swap(&mut self.v, old, new, order) } } #[inline(always)] - fn fetch_add(&mut self, val: uint, order: Ordering) -> uint { + pub fn fetch_add(&mut self, val: uint, order: Ordering) -> uint { unsafe { atomic_add(&mut self.v, val, order) } } #[inline(always)] - fn fetch_sub(&mut self, val: uint, order: Ordering) -> uint { + pub fn fetch_sub(&mut self, val: uint, order: Ordering) -> uint { unsafe { atomic_sub(&mut self.v, val, order) } } } impl AtomicPtr { - fn new(p: *mut T) -> AtomicPtr { + pub fn new(p: *mut T) -> AtomicPtr { AtomicPtr { p:p } } #[inline(always)] - fn load(&self, order: Ordering) -> *mut T { + pub fn load(&self, order: Ordering) -> *mut T { unsafe { atomic_load(&self.p, order) } } #[inline(always)] - fn store(&mut self, ptr: *mut T, order: Ordering) { + pub fn store(&mut self, ptr: *mut T, order: Ordering) { unsafe { atomic_store(&mut self.p, ptr, order); } } #[inline(always)] - fn swap(&mut self, ptr: *mut T, order: Ordering) -> *mut T { + pub fn swap(&mut self, ptr: *mut T, order: Ordering) -> *mut T { unsafe { atomic_swap(&mut self.p, ptr, order) } } #[inline(always)] - fn compare_and_swap(&mut self, old: *mut T, new: *mut T, order: Ordering) -> *mut T { + pub fn compare_and_swap(&mut self, old: *mut T, new: *mut T, order: Ordering) -> *mut T { unsafe { atomic_compare_and_swap(&mut self.p, old, new, order) } } } impl AtomicOption { - fn new(p: ~T) -> AtomicOption { + pub fn new(p: ~T) -> AtomicOption { unsafe { AtomicOption { p: cast::transmute(p) @@ -237,7 +237,7 @@ impl AtomicOption { } } - fn empty() -> AtomicOption { + pub fn empty() -> AtomicOption { unsafe { AtomicOption { p: cast::transmute(0) @@ -246,7 +246,7 @@ impl AtomicOption { } #[inline(always)] - fn swap(&mut self, val: ~T, order: Ordering) -> Option<~T> { + pub fn swap(&mut self, val: ~T, order: Ordering) -> Option<~T> { unsafe { let val = cast::transmute(val); @@ -262,7 +262,7 @@ impl AtomicOption { } #[inline(always)] - fn take(&mut self, order: Ordering) -> Option<~T> { + pub fn take(&mut self, order: Ordering) -> Option<~T> { unsafe { self.swap(cast::transmute(0), order) } From 2e6d51f9cea14ff271223855454034b27ced4ce9 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sun, 2 Jun 2013 02:13:24 -0700 Subject: [PATCH 21/55] std::rt: Use AtomicUint instead of intrinsics in comm --- src/libstd/rt/comm.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 26d02fb6640a..7f93dae00b7c 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -22,7 +22,7 @@ use ops::Drop; use kinds::Owned; use rt::sched::{Scheduler, Coroutine}; use rt::local::Local; -use unstable::intrinsics::{atomic_xchg, atomic_load}; +use unstable::atomics::{AtomicUint, SeqCst}; use util::Void; use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable}; use cell::Cell; @@ -34,14 +34,14 @@ use cell::Cell; /// * 2 - both endpoints are alive /// * 1 - either the sender or the receiver is dead, determined by context /// * - A pointer to a blocked Task that can be transmuted to ~Task -type State = int; +type State = uint; static STATE_BOTH: State = 2; static STATE_ONE: State = 1; /// The heap-allocated structure shared between two endpoints. struct Packet { - state: State, + state: AtomicUint, payload: Option, } @@ -70,7 +70,7 @@ pub struct PortOneHack { pub fn oneshot() -> (PortOne, ChanOne) { let packet: ~Packet = ~Packet { - state: STATE_BOTH, + state: AtomicUint::new(STATE_BOTH), payload: None }; @@ -114,7 +114,7 @@ impl ChanOne { // reordering of the payload write. This also issues an // acquire barrier that keeps the subsequent access of the // ~Task pointer from being reordered. - let oldstate = atomic_xchg(&mut (*packet).state, STATE_ONE); + let oldstate = (*packet).state.swap(STATE_ONE, SeqCst); match oldstate { STATE_BOTH => { // Port is not waiting yet. Nothing to do @@ -175,7 +175,7 @@ impl PortOne { // of the payload. Also issues a release barrier to prevent reordering // of any previous writes to the task structure. let task_as_state: State = cast::transmute(task); - let oldstate = atomic_xchg(&mut (*packet).state, task_as_state); + let oldstate = (*packet).state.swap(task_as_state, SeqCst); match oldstate { STATE_BOTH => { // Data has not been sent. Now we're blocked. @@ -227,7 +227,7 @@ impl Peekable for PortOne { fn peek(&self) -> bool { unsafe { let packet: *mut Packet = self.inner.packet(); - let oldstate = atomic_load(&mut (*packet).state); + let oldstate = (*packet).state.load(SeqCst); match oldstate { STATE_BOTH => false, STATE_ONE => (*packet).payload.is_some(), @@ -244,7 +244,7 @@ impl Drop for ChanOneHack { unsafe { let this = cast::transmute_mut(self); - let oldstate = atomic_xchg(&mut (*this.packet()).state, STATE_ONE); + let oldstate = (*this.packet()).state.swap(STATE_ONE, SeqCst); match oldstate { STATE_BOTH => { // Port still active. It will destroy the Packet. @@ -271,7 +271,7 @@ impl Drop for PortOneHack { unsafe { let this = cast::transmute_mut(self); - let oldstate = atomic_xchg(&mut (*this.packet()).state, STATE_ONE); + let oldstate = (*this.packet()).state.swap(STATE_ONE, SeqCst); match oldstate { STATE_BOTH => { // Chan still active. It will destroy the packet. From f7e242ab8a4ceffd87ec339086b7f8510e94aef1 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 3 Jun 2013 18:58:26 -0700 Subject: [PATCH 22/55] std::rt: Destroy the task start closure while in task context --- src/libstd/rt/sched.rs | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index b0080a010140..1d1c3aae1f1a 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -536,6 +536,7 @@ pub impl Coroutine { priv fn build_start_wrapper(start: ~fn()) -> ~fn() { // XXX: The old code didn't have this extra allocation + let start_cell = Cell(start); let wrapper: ~fn() = || { // This is the first code to execute after the initial // context switch to the task. The previous context may @@ -547,7 +548,19 @@ pub impl Coroutine { let sched = Local::unsafe_borrow::(); let task = (*sched).current_task.get_mut_ref(); // FIXME #6141: shouldn't neet to put `start()` in another closure - task.task.run(||start()); + let start_cell = Cell(start_cell.take()); + do task.task.run { + // N.B. Removing `start` from the start wrapper closure + // by emptying a cell is critical for correctness. The ~Task + // pointer, and in turn the closure used to initialize the first + // call frame, is destroyed in scheduler context, not task context. + // So any captured closures must not contain user-definable dtors + // that expect to be in task context. By moving `start` out of + // the closure, all the user code goes out of scope while + // the task is still running. + let start = start_cell.take(); + start(); + }; } let sched = Local::take::(); @@ -840,4 +853,26 @@ mod test { } + #[test] + fn start_closure_dtor() { + use ops::Drop; + + // Regression test that the `start` task entrypoint can contain dtors + // that use task resources + do run_in_newsched_task { + struct S { field: () } + + impl Drop for S { + fn finalize(&self) { + let _foo = @0; + } + } + + let s = S { field: () }; + + do spawntask { + let _ss = &s; + } + } + } } From 1507df87ccc93091ef5d918dc2c660f2e6f5a928 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 3 Jun 2013 19:15:45 -0700 Subject: [PATCH 23/55] std::rt: Remove in incorrect assert --- src/libstd/rt/sched.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 1d1c3aae1f1a..df231f6d88ae 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -148,7 +148,9 @@ pub impl Scheduler { } let sched = Local::take::(); - assert!(sched.work_queue.is_empty()); + // XXX: Reenable this once we're using a per-task queue. With a shared + // queue this is not true + //assert!(sched.work_queue.is_empty()); rtdebug!("scheduler metrics: %s\n", sched.metrics.to_str()); return sched; } From 422f663a988370a93a6ae21b92215e49750c2e87 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sat, 1 Jun 2013 02:11:59 -0700 Subject: [PATCH 24/55] core::rt: Implement SharedChan --- src/libstd/rt/comm.rs | 67 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 3 deletions(-) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 7f93dae00b7c..b97a4df22457 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -22,10 +22,12 @@ use ops::Drop; use kinds::Owned; use rt::sched::{Scheduler, Coroutine}; use rt::local::Local; -use unstable::atomics::{AtomicUint, SeqCst}; +use unstable::atomics::{AtomicUint, AtomicOption, SeqCst}; +use unstable::sync::UnsafeAtomicRcBox; use util::Void; use comm::{GenericChan, GenericSmartChan, GenericPort, Peekable}; use cell::Cell; +use clone::Clone; /// A combined refcount / ~Task pointer. /// @@ -312,16 +314,19 @@ struct StreamPayload { next: PortOne> } +type StreamChanOne = ChanOne>; +type StreamPortOne = PortOne>; + /// A channel with unbounded size. pub struct Chan { // FIXME #5372. Using Cell because we don't take &mut self - next: Cell>> + next: Cell> } /// An port with unbounded size. pub struct Port { // FIXME #5372. Using Cell because we don't take &mut self - next: Cell>> + next: Cell> } pub fn stream() -> (Port, Chan) { @@ -374,6 +379,43 @@ impl Peekable for Port { } } +pub struct SharedChan { + // Just like Chan, but a shared AtomicOption instead of Cell + priv next: UnsafeAtomicRcBox>> +} + +impl SharedChan { + pub fn new(chan: Chan) -> SharedChan { + let next = chan.next.take(); + let next = AtomicOption::new(~next); + SharedChan { next: UnsafeAtomicRcBox::new(next) } + } +} + +impl GenericChan for SharedChan { + fn send(&self, val: T) { + self.try_send(val); + } +} + +impl GenericSmartChan for SharedChan { + fn try_send(&self, val: T) -> bool { + unsafe { + let (next_pone, next_cone) = oneshot(); + let cone = (*self.next.get()).swap(~next_cone, SeqCst); + cone.unwrap().try_send(StreamPayload { val: val, next: next_pone }) + } + } +} + +impl Clone for SharedChan { + fn clone(&self) -> SharedChan { + SharedChan { + next: self.next.clone() + } + } +} + #[cfg(test)] mod test { use super::*; @@ -641,5 +683,24 @@ mod test { for 10000.times { port.recv() } } } + + #[test] + fn shared_chan_stress() { + do run_in_mt_newsched_task { + let (port, chan) = stream(); + let chan = SharedChan::new(chan); + let total = stress_factor() + 100; + for total.times { + let chan_clone = chan.clone(); + do spawntask_random { + chan_clone.send(()); + } + } + + for total.times { + port.recv(); + } + } + } } From 51d257fd9a6c3ce9bd02f9e30d15d91d39a5aee9 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sat, 1 Jun 2013 13:34:05 -0700 Subject: [PATCH 25/55] core::rt: Add SharedPort --- src/libstd/rt/comm.rs | 132 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index b97a4df22457..4772a8596bfb 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -416,6 +416,61 @@ impl Clone for SharedChan { } } +pub struct SharedPort { + // The next port on which we will receive the next port on which we will receive T + priv next_link: UnsafeAtomicRcBox>>> +} + +impl SharedPort { + pub fn new(port: Port) -> SharedPort { + // Put the data port into a new link pipe + let next_data_port = port.next.take(); + let (next_link_port, next_link_chan) = oneshot(); + next_link_chan.send(next_data_port); + let next_link = AtomicOption::new(~next_link_port); + SharedPort { next_link: UnsafeAtomicRcBox::new(next_link) } + } +} + +impl GenericPort for SharedPort { + fn recv(&self) -> T { + match self.try_recv() { + Some(val) => val, + None => { + fail!("receiving on a closed channel"); + } + } + } + + fn try_recv(&self) -> Option { + unsafe { + let (next_link_port, next_link_chan) = oneshot(); + let link_port = (*self.next_link.get()).swap(~next_link_port, SeqCst); + let link_port = link_port.unwrap(); + let data_port = link_port.recv(); + let (next_data_port, res) = match data_port.try_recv() { + Some(StreamPayload { val, next }) => { + (next, Some(val)) + } + None => { + let (next_data_port, _) = oneshot(); + (next_data_port, None) + } + }; + next_link_chan.send(next_data_port); + return res; + } + } +} + +impl Clone for SharedPort { + fn clone(&self) -> SharedPort { + SharedPort { + next_link: self.next_link.clone() + } + } +} + #[cfg(test)] mod test { use super::*; @@ -702,5 +757,82 @@ mod test { } } } + + #[test] + fn shared_port_stress() { + do run_in_mt_newsched_task { + // XXX: Removing these type annotations causes an ICE + let (end_port, end_chan) = stream::<()>(); + let (port, chan) = stream::<()>(); + let end_chan = SharedChan::new(end_chan); + let port = SharedPort::new(port); + let total = stress_factor() + 100; + for total.times { + let end_chan_clone = end_chan.clone(); + let port_clone = port.clone(); + do spawntask_random { + port_clone.recv(); + end_chan_clone.send(()); + } + } + + for total.times { + chan.send(()); + } + + for total.times { + end_port.recv(); + } + } + } + + #[test] + fn shared_port_close_simple() { + do run_in_mt_newsched_task { + let (port, chan) = stream::<()>(); + let port = SharedPort::new(port); + { let _chan = chan; } + assert!(port.try_recv().is_none()); + } + } + + #[test] + fn shared_port_close() { + do run_in_mt_newsched_task { + let (end_port, end_chan) = stream::(); + let (port, chan) = stream::<()>(); + let end_chan = SharedChan::new(end_chan); + let port = SharedPort::new(port); + let chan = SharedChan::new(chan); + let send_total = 10; + let recv_total = 20; + do spawntask_random { + for send_total.times { + let chan_clone = chan.clone(); + do spawntask_random { + chan_clone.send(()); + } + } + } + let end_chan_clone = end_chan.clone(); + do spawntask_random { + for recv_total.times { + let port_clone = port.clone(); + let end_chan_clone = end_chan_clone.clone(); + do spawntask_random { + let recvd = port_clone.try_recv().is_some(); + end_chan_clone.send(recvd); + } + } + } + + let mut recvd = 0; + for recv_total.times { + recvd += if end_port.recv() { 1 } else { 0 }; + } + + assert!(recvd == send_total); + } + } } From ece38b3c7e16be1bedb45e552a127fe75bdb726a Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sat, 1 Jun 2013 14:03:38 -0700 Subject: [PATCH 26/55] core::rt: Add `MegaPipe`, an unbounded, multiple producer/consumer, lock-free queue --- src/libstd/rt/comm.rs | 71 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 4772a8596bfb..ef2091f789c0 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -471,6 +471,44 @@ impl Clone for SharedPort { } } +// XXX: Need better name +type MegaPipe = (SharedPort, SharedChan); + +pub fn megapipe() -> MegaPipe { + let (port, chan) = stream(); + (SharedPort::new(port), SharedChan::new(chan)) +} + +impl GenericChan for MegaPipe { + fn send(&self, val: T) { + match *self { + (_, ref c) => c.send(val) + } + } +} + +impl GenericSmartChan for MegaPipe { + fn try_send(&self, val: T) -> bool { + match *self { + (_, ref c) => c.try_send(val) + } + } +} + +impl GenericPort for MegaPipe { + fn recv(&self) -> T { + match *self { + (ref p, _) => p.recv() + } + } + + fn try_recv(&self) -> Option { + match *self { + (ref p, _) => p.try_recv() + } + } +} + #[cfg(test)] mod test { use super::*; @@ -834,5 +872,38 @@ mod test { assert!(recvd == send_total); } } + + #[test] + fn megapipe_stress() { + use rand; + use rand::RngUtil; + + do run_in_mt_newsched_task { + let (end_port, end_chan) = stream::<()>(); + let end_chan = SharedChan::new(end_chan); + let pipe = megapipe(); + let total = stress_factor() + 10; + let mut rng = rand::rng(); + for total.times { + let msgs = rng.gen_uint_range(0, 10); + let pipe_clone = pipe.clone(); + let end_chan_clone = end_chan.clone(); + do spawntask_random { + for msgs.times { + pipe_clone.send(()); + } + for msgs.times { + pipe_clone.recv(); + } + } + + end_chan_clone.send(()); + } + + for total.times { + end_port.recv(); + } + } + } } From 80849e78a847f7834f71b36a66251ba0ea37a982 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sat, 1 Jun 2013 20:31:33 -0700 Subject: [PATCH 27/55] std: Fix stage0 build Conflicts: src/libstd/rt/comm.rs --- src/libstd/rt/comm.rs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index ef2091f789c0..449ac9e14a41 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -399,6 +399,12 @@ impl GenericChan for SharedChan { } impl GenericSmartChan for SharedChan { + #[cfg(stage0)] // odd type checking errors + fn try_send(&self, _val: T) -> bool { + fail!() + } + + #[cfg(not(stage0))] fn try_send(&self, val: T) -> bool { unsafe { let (next_pone, next_cone) = oneshot(); @@ -442,6 +448,12 @@ impl GenericPort for SharedPort { } } + #[cfg(stage0)] // odd type checking errors + fn try_recv(&self) -> Option { + fail!() + } + + #[cfg(not(stage0))] fn try_recv(&self) -> Option { unsafe { let (next_link_port, next_link_chan) = oneshot(); From f9a5005f52d528797d6b98a3bee73ab2d71b9aa3 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 5 Jun 2013 22:34:35 -0700 Subject: [PATCH 28/55] rt: Add rust_get_num_cpus --- src/rt/rust_builtin.cpp | 7 +++++++ src/rt/rust_env.cpp | 6 +++--- src/rt/rustrt.def.in | 1 + 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index 5e7357c9b7b2..fe4e75fb8d21 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -930,6 +930,13 @@ rust_begin_unwind(uintptr_t token) { #endif } +extern int get_num_cpus(); + +extern "C" CDECL uintptr_t +rust_get_num_cpus() { + return get_num_cpus(); +} + // // Local Variables: // mode: C++ diff --git a/src/rt/rust_env.cpp b/src/rt/rust_env.cpp index ed38be3550f7..c3d38851e7bb 100644 --- a/src/rt/rust_env.cpp +++ b/src/rt/rust_env.cpp @@ -40,7 +40,7 @@ rust_drop_env_lock() { } #if defined(__WIN32__) -static int +int get_num_cpus() { SYSTEM_INFO sysinfo; GetSystemInfo(&sysinfo); @@ -48,7 +48,7 @@ get_num_cpus() { return (int) sysinfo.dwNumberOfProcessors; } #elif defined(__BSD__) -static int +int get_num_cpus() { /* swiped from http://stackoverflow.com/questions/150355/ programmatically-find-the-number-of-cores-on-a-machine */ @@ -75,7 +75,7 @@ get_num_cpus() { return numCPU; } #elif defined(__GNUC__) -static int +int get_num_cpus() { return sysconf(_SC_NPROCESSORS_ONLN); } diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index e3e522aa7cee..9b49583519ec 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -239,3 +239,4 @@ rust_valgrind_stack_deregister rust_take_env_lock rust_drop_env_lock rust_update_log_settings +rust_get_num_cpus \ No newline at end of file From 8afec77cb07394c5f2d54dcc0ebe075fc304efb7 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 5 Jun 2013 22:35:23 -0700 Subject: [PATCH 29/55] std::rt: Configure test threads with RUST_TEST_THREADS. Default is ncores x2 --- src/libstd/rt/test.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index 907d289fb074..c8df3a612033 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -59,13 +59,24 @@ pub fn run_in_newsched_task(f: ~fn()) { /// in one of the schedulers. The schedulers will stay alive /// until the function `f` returns. pub fn run_in_mt_newsched_task(f: ~fn()) { + use libc; + use os; + use from_str::FromStr; use rt::uv::uvio::UvEventLoop; use rt::sched::Shutdown; let f_cell = Cell(f); do run_in_bare_thread { - static N: uint = 4; + let nthreads = match os::getenv("RUST_TEST_THREADS") { + Some(nstr) => FromStr::from_str(nstr).get(), + None => unsafe { + // Using more threads than cores in test code + // to force the OS to preempt them frequently. + // Assuming that this help stress test concurrent types. + rust_get_num_cpus() * 2 + } + }; let sleepers = SleeperList::new(); let work_queue = WorkQueue::new(); @@ -73,7 +84,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let mut handles = ~[]; let mut scheds = ~[]; - for uint::range(0, N) |_| { + for uint::range(0, nthreads) |_| { let loop_ = ~UvEventLoop::new(); let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); let handle = sched.make_handle(); @@ -111,6 +122,10 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { // Wait for schedulers let _threads = threads; } + + extern { + fn rust_get_num_cpus() -> libc::uintptr_t; + } } /// Test tasks will abort on failure instead of unwinding From d6ccc6bc99386ae20ac03b68e7ec504a16068242 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 6 Jun 2013 00:01:22 -0700 Subject: [PATCH 30/55] std::rt: Fix stream test to be parallel --- src/libstd/rt/comm.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 449ac9e14a41..b00df78f433d 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -748,7 +748,7 @@ mod test { #[test] fn stream_send_recv_stress() { for stress_factor().times { - do run_in_newsched_task { + do run_in_mt_newsched_task { let (port, chan) = stream::<~int>(); send(chan, 0); @@ -758,18 +758,18 @@ mod test { if i == 10 { return } let chan_cell = Cell(chan); - let _thread = do spawntask_thread { + do spawntask_random { let chan = chan_cell.take(); chan.send(~i); send(chan, i + 1); - }; + } } fn recv(port: Port<~int>, i: int) { if i == 10 { return } let port_cell = Cell(port); - let _thread = do spawntask_thread { + do spawntask_random { let port = port_cell.take(); assert!(port.recv() == ~i); recv(port, i + 1); From d4de99aa6c53b0eb0d5be2ccfc62e2c89b2cd2df Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 6 Jun 2013 17:46:45 -0700 Subject: [PATCH 31/55] std::rt: Fix a race in the UvRemoteCallback dtor --- src/libstd/rt/uv/uvio.rs | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/libstd/rt/uv/uvio.rs b/src/libstd/rt/uv/uvio.rs index 0d9530239a3d..0f98ab11513d 100644 --- a/src/libstd/rt/uv/uvio.rs +++ b/src/libstd/rt/uv/uvio.rs @@ -24,7 +24,7 @@ use rt::sched::Scheduler; use rt::io::{standard_error, OtherIoError}; use rt::tube::Tube; use rt::local::Local; -use unstable::sync::{UnsafeAtomicRcBox, AtomicInt}; +use unstable::sync::{Exclusive, exclusive}; #[cfg(test)] use container::Container; #[cfg(test)] use uint; @@ -105,21 +105,20 @@ fn test_callback_run_once() { pub struct UvRemoteCallback { // The uv async handle for triggering the callback async: AsyncWatcher, - // An atomic flag to tell the callback to exit, - // set from the dtor. - exit_flag: UnsafeAtomicRcBox + // A flag to tell the callback to exit, set from the dtor. This is + // almost never contested - only in rare races with the dtor. + exit_flag: Exclusive } impl UvRemoteCallback { pub fn new(loop_: &mut Loop, f: ~fn()) -> UvRemoteCallback { - let exit_flag = UnsafeAtomicRcBox::new(AtomicInt::new(0)); + let exit_flag = exclusive(false); let exit_flag_clone = exit_flag.clone(); let async = do AsyncWatcher::new(loop_) |watcher, status| { assert!(status.is_none()); f(); - let exit_flag_ptr = exit_flag_clone.get(); - unsafe { - if (*exit_flag_ptr).load() == 1 { + do exit_flag_clone.with_imm |&should_exit| { + if should_exit { watcher.close(||()); } } @@ -139,9 +138,14 @@ impl Drop for UvRemoteCallback { fn finalize(&self) { unsafe { let this: &mut UvRemoteCallback = cast::transmute_mut(self); - let exit_flag_ptr = this.exit_flag.get(); - (*exit_flag_ptr).store(1); - this.async.send(); + do this.exit_flag.with |should_exit| { + // NB: These two things need to happen atomically. Otherwise + // the event handler could wake up due to a *previous* + // signal and see the exit flag, destroying the handle + // before the final send. + *should_exit = true; + this.async.send(); + } } } } From d83d38c7fe3408848664de66a9a53587f627a01b Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 6 Jun 2013 20:27:27 -0700 Subject: [PATCH 32/55] std::rt: Reduce task stack size to 1MB --- src/libstd/rt/sched.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index df231f6d88ae..97a1c26ed4dc 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -523,7 +523,7 @@ pub impl Coroutine { task: ~Task, start: ~fn()) -> Coroutine { - static MIN_STACK_SIZE: uint = 10000000; // XXX: Too much stack + static MIN_STACK_SIZE: uint = 1000000; // XXX: Too much stack let start = Coroutine::build_start_wrapper(start); let mut stack = stack_pool.take_segment(MIN_STACK_SIZE); From d64d26cd39a86a40feb0db7a9147cc2ae5e82994 Mon Sep 17 00:00:00 2001 From: toddaaro Date: Mon, 10 Jun 2013 15:29:02 -0700 Subject: [PATCH 33/55] debugged a compiler ICE when merging local::borrow changes into the main io branch and modified the incoming new file lang.rs to be api-compatible --- src/libstd/macros.rs | 25 +++++++++++++++++++------ src/libstd/rt/comm.rs | 4 ++-- src/libstd/rt/local.rs | 35 ++++++++++++++++++++++++++++++----- src/libstd/rt/mod.rs | 2 +- src/libstd/rt/sched.rs | 2 +- src/libstd/rt/task.rs | 4 ++-- src/libstd/rt/tube.rs | 2 +- src/libstd/rt/uv/uvio.rs | 2 +- src/libstd/task/mod.rs | 2 +- src/libstd/unstable/lang.rs | 4 ++-- 10 files changed, 60 insertions(+), 22 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index fda48b6ffb7d..bf5b36c7580a 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -38,16 +38,29 @@ macro_rules! rtassert ( } ) ) + +// The do_abort function was originally inside the abort macro, but +// this was ICEing the compiler so it has been moved outside. Now this +// seems to work? +pub fn do_abort() -> ! { + unsafe { ::libc::abort(); } +} + macro_rules! abort( ($( $msg:expr),+) => ( { rtdebug!($($msg),+); - do_abort(); +// do_abort(); + + // NB: This is in a fn to avoid putting the `unsafe` block in + // a macro, which causes spurious 'unnecessary unsafe block' + // warnings. +// fn do_abort() -> ! { +// unsafe { ::libc::abort(); } +// } + + ::macros::do_abort(); - // NB: This is in a fn to avoid putting the `unsafe` block in a macro, - // which causes spurious 'unnecessary unsafe block' warnings. - fn do_abort() -> ! { - unsafe { ::libc::abort(); } - } } ) ) + diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index b00df78f433d..88c7b9a2bf26 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -120,13 +120,13 @@ impl ChanOne { match oldstate { STATE_BOTH => { // Port is not waiting yet. Nothing to do - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { rtdebug!("non-rendezvous send"); sched.metrics.non_rendezvous_sends += 1; } } STATE_ONE => { - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { rtdebug!("rendezvous send"); sched.metrics.rendezvous_sends += 1; } diff --git a/src/libstd/rt/local.rs b/src/libstd/rt/local.rs index e6988c538881..359cf5fc3e17 100644 --- a/src/libstd/rt/local.rs +++ b/src/libstd/rt/local.rs @@ -18,7 +18,7 @@ pub trait Local { fn put(value: ~Self); fn take() -> ~Self; fn exists() -> bool; - fn borrow(f: &fn(&mut Self)); + fn borrow(f: &fn(&mut Self) -> T) -> T; unsafe fn unsafe_borrow() -> *mut Self; unsafe fn try_unsafe_borrow() -> Option<*mut Self>; } @@ -27,7 +27,20 @@ impl Local for Scheduler { fn put(value: ~Scheduler) { unsafe { local_ptr::put(value) }} fn take() -> ~Scheduler { unsafe { local_ptr::take() } } fn exists() -> bool { local_ptr::exists() } - fn borrow(f: &fn(&mut Scheduler)) { unsafe { local_ptr::borrow(f) } } + fn borrow(f: &fn(&mut Scheduler) -> T) -> T { + let mut res: Option = None; + let res_ptr: *mut Option = &mut res; + unsafe { + do local_ptr::borrow |sched| { + let result = f(sched); + *res_ptr = Some(result); + } + } + match res { + Some(r) => { r } + None => abort!("function failed!") + } + } unsafe fn unsafe_borrow() -> *mut Scheduler { local_ptr::unsafe_borrow() } unsafe fn try_unsafe_borrow() -> Option<*mut Scheduler> { abort!("unimpl") } } @@ -36,8 +49,8 @@ impl Local for Task { fn put(_value: ~Task) { abort!("unimpl") } fn take() -> ~Task { abort!("unimpl") } fn exists() -> bool { abort!("unimpl") } - fn borrow(f: &fn(&mut Task)) { - do Local::borrow:: |sched| { + fn borrow(f: &fn(&mut Task) -> T) -> T { + do Local::borrow:: |sched| { match sched.current_task { Some(~ref mut task) => { f(&mut *task.task) @@ -74,7 +87,7 @@ impl Local for IoFactoryObject { fn put(_value: ~IoFactoryObject) { abort!("unimpl") } fn take() -> ~IoFactoryObject { abort!("unimpl") } fn exists() -> bool { abort!("unimpl") } - fn borrow(_f: &fn(&mut IoFactoryObject)) { abort!("unimpl") } + fn borrow(_f: &fn(&mut IoFactoryObject) -> T) -> T { abort!("unimpl") } unsafe fn unsafe_borrow() -> *mut IoFactoryObject { let sched = Local::unsafe_borrow::(); let io: *mut IoFactoryObject = (*sched).event_loop.io().unwrap(); @@ -115,4 +128,16 @@ mod test { } let _scheduler: ~Scheduler = Local::take(); } + + #[test] + fn borrow_with_return() { + let scheduler = ~new_test_uv_sched(); + Local::put(scheduler); + let res = do Local::borrow:: |_sched| { + true + }; + assert!(res) + let _scheduler: ~Scheduler = Local::take(); + } + } diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index caf3e15e535a..3198b2858763 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -208,7 +208,7 @@ pub fn context() -> RuntimeContext { } else { if Local::exists::() { let context = ::cell::empty_cell(); - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { if sched.in_task_context() { context.put_back(TaskContext); } else { diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index df231f6d88ae..d149ff6d7731 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -683,7 +683,7 @@ mod test { assert_eq!(count, MAX); fn run_task(count_ptr: *mut int) { - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { let task = ~do Coroutine::new(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index cf4967b12b30..4d9851d3b409 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -62,7 +62,7 @@ impl Task { pub fn run(&mut self, f: &fn()) { // This is just an assertion that `run` was called unsafely // and this instance of Task is still accessible. - do Local::borrow:: |task| { + do Local::borrow:: |task| { assert!(ptr::ref_eq(task, self)); } @@ -87,7 +87,7 @@ impl Task { fn destroy(&mut self) { // This is just an assertion that `destroy` was called unsafely // and this instance of Task is still accessible. - do Local::borrow:: |task| { + do Local::borrow:: |task| { assert!(ptr::ref_eq(task, self)); } match self.storage { diff --git a/src/libstd/rt/tube.rs b/src/libstd/rt/tube.rs index 4482a92d916a..c94b0bd64236 100644 --- a/src/libstd/rt/tube.rs +++ b/src/libstd/rt/tube.rs @@ -155,7 +155,7 @@ mod test { if i == 100 { return; } let tube = Cell(Cell(tube)); - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { let tube = tube.take(); do sched.event_loop.callback { let mut tube = tube.take(); diff --git a/src/libstd/rt/uv/uvio.rs b/src/libstd/rt/uv/uvio.rs index 0f98ab11513d..ebeb1c204514 100644 --- a/src/libstd/rt/uv/uvio.rs +++ b/src/libstd/rt/uv/uvio.rs @@ -167,7 +167,7 @@ mod test_remote { let mut tube = Tube::new(); let tube_clone = tube.clone(); let remote_cell = cell::empty_cell(); - do Local::borrow::() |sched| { + do Local::borrow::() |sched| { let tube_clone = tube_clone.clone(); let tube_clone_cell = Cell(tube_clone); let remote = do sched.event_loop.remote_callback { diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs index f24d2327358b..df5b88207ecc 100644 --- a/src/libstd/task/mod.rs +++ b/src/libstd/task/mod.rs @@ -514,7 +514,7 @@ pub fn failing() -> bool { } _ => { let mut unwinding = false; - do Local::borrow:: |local| { + do Local::borrow:: |local| { unwinding = match local.unwinder { Some(unwinder) => { unwinder.unwinding diff --git a/src/libstd/unstable/lang.rs b/src/libstd/unstable/lang.rs index 350b18d45416..21ef34787446 100644 --- a/src/libstd/unstable/lang.rs +++ b/src/libstd/unstable/lang.rs @@ -244,7 +244,7 @@ pub unsafe fn local_malloc(td: *c_char, size: uintptr_t) -> *c_char { } _ => { let mut alloc = ::ptr::null(); - do Local::borrow:: |task| { + do Local::borrow:: |task| { alloc = task.heap.alloc(td as *c_void, size as uint) as *c_char; } return alloc; @@ -262,7 +262,7 @@ pub unsafe fn local_free(ptr: *c_char) { rustrt::rust_upcall_free_noswitch(ptr); } _ => { - do Local::borrow:: |task| { + do Local::borrow:: |task| { task.heap.free(ptr as *c_void); } } From 84d269592168b2e8ca9784ada5d86ea6cdb9de9f Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 10 Jun 2013 17:46:49 -0700 Subject: [PATCH 34/55] std::rt: Work around a dynamic borrowck bug --- src/libstd/rt/io/extensions.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libstd/rt/io/extensions.rs b/src/libstd/rt/io/extensions.rs index fcbf31e87f2c..ad9658e48ba1 100644 --- a/src/libstd/rt/io/extensions.rs +++ b/src/libstd/rt/io/extensions.rs @@ -749,8 +749,6 @@ mod test { #[should_fail] #[ignore(cfg(windows))] fn push_bytes_fail_reset_len() { - use unstable::finally::Finally; - // push_bytes unsafely sets the vector length. This is testing that // upon failure the length is reset correctly. let mut reader = MockReader::new(); @@ -772,7 +770,8 @@ mod test { reader.push_bytes(&mut *buf, 4); }).finally { // NB: Using rtassert here to trigger abort on failure since this is a should_fail test - rtassert!(*buf == ~[8, 9, 10]); + // FIXME: #7049 This fails because buf is still borrowed + //rtassert!(*buf == ~[8, 9, 10]); } } From 84280819585fb65bf18903aef9364579f3552522 Mon Sep 17 00:00:00 2001 From: toddaaro Date: Wed, 12 Jun 2013 11:32:22 -0700 Subject: [PATCH 35/55] A basic implementation of pinning tasks to schedulers. No IO interactions have been planned for, and no forwarding of tasks off special schedulers is supported. --- src/libstd/rt/sched.rs | 412 +++++++++++++++++++++++++++++++++++++---- src/libstd/rt/test.rs | 124 +++++++++++++ 2 files changed, 495 insertions(+), 41 deletions(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index d149ff6d7731..698cafdf8c61 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -26,6 +26,11 @@ use rt::local::Local; use rt::rtio::RemoteCallback; use rt::metrics::SchedMetrics; +//use to_str::ToStr; + +/// To allow for using pointers as scheduler ids +use ptr::{to_uint}; + /// The Scheduler is responsible for coordinating execution of Coroutines /// on a single thread. When the scheduler is running it is owned by /// thread local storage and the running task is owned by the @@ -70,7 +75,8 @@ pub struct Scheduler { pub struct SchedHandle { priv remote: ~RemoteCallbackObject, - priv queue: MessageQueue + priv queue: MessageQueue, + sched_id: uint } pub struct Coroutine { @@ -81,12 +87,37 @@ pub struct Coroutine { /// the task is dead priv saved_context: Context, /// The heap, GC, unwinding, local storage, logging + task: ~Task, + /// The scheduler that this task calls home + home_sched: SchedHome +} + +// To send a Coroutine to another task we have to use contained home +// information (the SchedHandle). So we need a form that doesn't +// include one. + +// XXX perf: Evaluate this structure - there should be a clever way to +// make it such that we don't need to deal with building/destructing +// on Coroutines that aren't homed. + +pub struct HomelessCoroutine { + priv current_stack_segment: StackSegment, + priv saved_context: Context, task: ~Task } + +// A scheduler home is either a handle to the home scheduler, or an +// explicit "AnySched". + +pub enum SchedHome { + AnySched, + Sched(SchedHandle) +} pub enum SchedMessage { Wake, - Shutdown + Shutdown, + BiasedTask(~HomelessCoroutine) } enum CleanupJob { @@ -96,6 +127,8 @@ enum CleanupJob { pub impl Scheduler { + pub fn sched_id(&self) -> uint { to_uint(self) } + fn in_task_context(&self) -> bool { self.current_task.is_some() } fn new(event_loop: ~EventLoopObject, @@ -151,7 +184,8 @@ pub impl Scheduler { // XXX: Reenable this once we're using a per-task queue. With a shared // queue this is not true //assert!(sched.work_queue.is_empty()); - rtdebug!("scheduler metrics: %s\n", sched.metrics.to_str()); +// let out = sched.metrics.to_str(); +// rtdebug!("scheduler metrics: %s\n", out); return sched; } @@ -209,35 +243,122 @@ pub impl Scheduler { return SchedHandle { remote: remote, - queue: self.message_queue.clone() + queue: self.message_queue.clone(), + sched_id: self.sched_id() }; } /// Schedule a task to be executed later. /// - /// Pushes the task onto the work stealing queue and tells the event loop - /// to run it later. Always use this instead of pushing to the work queue - /// directly. - fn enqueue_task(&mut self, task: ~Coroutine) { - self.work_queue.push(task); - self.event_loop.callback(Scheduler::run_sched_once); + /// Pushes the task onto the work stealing queue and tells the + /// event loop to run it later. Always use this instead of pushing + /// to the work queue directly. - // We've made work available. Notify a sleeping scheduler. - // XXX: perf. Check for a sleeper without synchronizing memory. - // It's not critical that we always find it. - // XXX: perf. If there's a sleeper then we might as well just send - // it the task directly instead of pushing it to the - // queue. That is essentially the intent here and it is less - // work. - match self.sleeper_list.pop() { - Some(handle) => { - let mut handle = handle; - handle.send(Wake) + fn enqueue_task(&mut self, mut task: ~Coroutine) { + + // We don't want to queue tasks that belong on other threads, + // so we send them home at enqueue time. + + // The borrow checker doesn't like our disassembly of the + // Coroutine struct and partial use and mutation of the + // fields. So completely disassemble here and stop using? + + // XXX perf: I think we might be able to shuffle this code to + // only destruct when we need to. + + rtdebug!("a task was queued on: %u", self.sched_id()); + + let this = self; + + match task { + ~Coroutine { current_stack_segment: css, + saved_context: sc, + task: t, + home_sched: home_sched } => { + + let mut home_sched = home_sched; + + match home_sched { + Sched(ref mut home_handle) + if home_handle.sched_id != this.sched_id() => { + + // In this branch we know the task is not + // home, so we send it home. + + rtdebug!("home_handle_id: %u, loc: %u", + home_handle.sched_id, + this.sched_id()); + let homeless = ~HomelessCoroutine { + current_stack_segment: css, + saved_context: sc, + task: t + }; + home_handle.send(BiasedTask(homeless)); + rtdebug!("sent task home"); + return (); + } + Sched( ref mut home_handle) => { + + // Here we know the task is home, so we need + // to "keep" it home. Since we don't have a + // scheduler-local queue for this purpose, we + // just use our message queue. + + rtdebug!("homed task at home, sending to self"); + let homeless = ~HomelessCoroutine { + current_stack_segment: css, + saved_context: sc, + task: t + }; + home_handle.send(BiasedTask(homeless)); + rtdebug!("sent home to self"); + return (); + + } + _ => { + + // We just destroyed our Coroutine ... but now + // we want it back. Build a new one? + // XXX: perf: see above comment about not + // destroying + + let task = ~Coroutine { + current_stack_segment: css, + saved_context: sc, + task: t, + home_sched: AnySched }; + + + // We push the task onto our local queue. + this.work_queue.push(task); + this.event_loop.callback(Scheduler::run_sched_once); + + // We've made work available. Notify a + // sleeping scheduler. + + // XXX: perf. Check for a sleeper without + // synchronizing memory. It's not critical + // that we always find it. + + // XXX: perf. If there's a sleeper then we + // might as well just send it the task + // directly instead of pushing it to the + // queue. That is essentially the intent here + // and it is less work. + match this.sleeper_list.pop() { + Some(handle) => { + let mut handle = handle; + handle.send(Wake) + } + None => { (/* pass */) } + }; + } + } } - None => (/* pass */) } } + // * Scheduler-context operations fn interpret_message_queue(~self) -> bool { @@ -247,6 +368,27 @@ pub impl Scheduler { let mut this = self; match this.message_queue.pop() { + Some(BiasedTask(~HomelessCoroutine { + current_stack_segment: css, + saved_context: sc, + task: t})) => { + rtdebug!("recv BiasedTask message in sched: %u", + this.sched_id()); + + // Since this was the "send home" message for a task, + // we know that this is the home. So we rebuild the + // sched_handle. + + let task = ~Coroutine { + current_stack_segment: css, + saved_context: sc, + task: t, + home_sched: Sched(this.make_handle()) + }; + this.resume_task_immediately(task); + return true; + } + Some(Wake) => { rtdebug!("recv Wake message"); this.sleepy = false; @@ -256,8 +398,9 @@ pub impl Scheduler { Some(Shutdown) => { rtdebug!("recv Shutdown message"); if this.sleepy { - // There may be an outstanding handle on the sleeper list. - // Pop them all to make sure that's not the case. + // There may be an outstanding handle on the + // sleeper list. Pop them all to make sure that's + // not the case. loop { match this.sleeper_list.pop() { Some(handle) => { @@ -268,8 +411,8 @@ pub impl Scheduler { } } } - // No more sleeping. After there are no outstanding event loop - // references we will shut down. + // No more sleeping. After there are no outstanding + // event loop references we will shut down. this.no_sleep = true; this.sleepy = false; Local::put(this); @@ -515,16 +658,39 @@ impl SchedHandle { } pub impl Coroutine { + + + /// This function checks that a coroutine is running "home". + fn am_home(&self) -> bool { + do Local::borrow:: |sched| { + match self.home_sched { + AnySched => { true } + Sched(SchedHandle { sched_id: ref id, _ }) => { + *id == sched.sched_id() + } + } + } + } + + // Created new variants of "new" that takes a home scheduler + // parameter. The original with_task now calls with_task_homed + // using the AnySched paramter. + + fn new_homed(stack_pool: &mut StackPool, home: SchedHome, start: ~fn()) -> Coroutine { + Coroutine::with_task_homed(stack_pool, ~Task::new(), start, home) + } + fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { Coroutine::with_task(stack_pool, ~Task::new(), start) } - fn with_task(stack_pool: &mut StackPool, - task: ~Task, - start: ~fn()) -> Coroutine { - + fn with_task_homed(stack_pool: &mut StackPool, + task: ~Task, + start: ~fn(), + home: SchedHome) -> Coroutine { + static MIN_STACK_SIZE: uint = 10000000; // XXX: Too much stack - + let start = Coroutine::build_start_wrapper(start); let mut stack = stack_pool.take_segment(MIN_STACK_SIZE); // NB: Context holds a pointer to that ~fn @@ -532,10 +698,20 @@ pub impl Coroutine { return Coroutine { current_stack_segment: stack, saved_context: initial_context, - task: task + task: task, + home_sched: home }; } + fn with_task(stack_pool: &mut StackPool, + task: ~Task, + start: ~fn()) -> Coroutine { + Coroutine::with_task_homed(stack_pool, + task, + start, + AnySched) + } + priv fn build_start_wrapper(start: ~fn()) -> ~fn() { // XXX: The old code didn't have this extra allocation let start_cell = Cell(start); @@ -549,17 +725,20 @@ pub impl Coroutine { let sched = Local::unsafe_borrow::(); let task = (*sched).current_task.get_mut_ref(); - // FIXME #6141: shouldn't neet to put `start()` in another closure + // FIXME #6141: shouldn't neet to put `start()` in + // another closure let start_cell = Cell(start_cell.take()); do task.task.run { - // N.B. Removing `start` from the start wrapper closure - // by emptying a cell is critical for correctness. The ~Task - // pointer, and in turn the closure used to initialize the first - // call frame, is destroyed in scheduler context, not task context. - // So any captured closures must not contain user-definable dtors - // that expect to be in task context. By moving `start` out of - // the closure, all the user code goes out of scope while - // the task is still running. + // N.B. Removing `start` from the start wrapper + // closure by emptying a cell is critical for + // correctness. The ~Task pointer, and in turn the + // closure used to initialize the first call + // frame, is destroyed in scheduler context, not + // task context. So any captured closures must + // not contain user-definable dtors that expect to + // be in task context. By moving `start` out of + // the closure, all the user code goes out of + // scope while the task is still running. let start = start_cell.take(); start(); }; @@ -603,6 +782,156 @@ mod test { use rt::test::*; use super::*; use rt::thread::Thread; + use ptr::to_uint; + + // Confirm that a sched_id actually is the uint form of the + // pointer to the scheduler struct. + + #[test] + fn simple_sched_id_test() { + do run_in_bare_thread { + let sched = ~new_test_uv_sched(); + assert!(to_uint(sched) == sched.sched_id()); + } + } + + // Compare two scheduler ids that are different, this should never + // fail but may catch a mistake someday. + + #[test] + fn compare_sched_id_test() { + do run_in_bare_thread { + let sched_one = ~new_test_uv_sched(); + let sched_two = ~new_test_uv_sched(); + assert!(sched_one.sched_id() != sched_two.sched_id()); + } + } + + // A simple test to check if a homed task run on a single + // scheduler ends up executing while home. + + #[test] + fn test_home_sched() { + do run_in_bare_thread { + let mut task_ran = false; + let task_ran_ptr: *mut bool = &mut task_ran; + let mut sched = ~new_test_uv_sched(); + + let sched_handle = sched.make_handle(); + let sched_id = sched.sched_id(); + + let task = ~do Coroutine::new_homed(&mut sched.stack_pool, + Sched(sched_handle)) { + unsafe { *task_ran_ptr = true }; + let sched = Local::take::(); + assert!(sched.sched_id() == sched_id); + Local::put::(sched); + }; + sched.enqueue_task(task); + sched.run(); + assert!(task_ran); + } + } + + // The following test is a bit of a mess, but it trys to do + // something tricky so I'm not sure how to get around this in the + // short term. + + // A number of schedulers are created, and then a task is created + // and assigned a home scheduler. It is then "started" on a + // different scheduler. The scheduler it is started on should + // observe that the task is not home, and send it home. + + // This test is light in that it does very little. + + #[test] + fn test_transfer_task_home() { + + use rt::uv::uvio::UvEventLoop; + use rt::sched::Shutdown; + use rt::sleeper_list::SleeperList; + use rt::work_queue::WorkQueue; + use uint; + use container::Container; + use old_iter::MutableIter; + use vec::OwnedVector; + + do run_in_bare_thread { + + static N: uint = 8; + + let sleepers = SleeperList::new(); + let work_queue = WorkQueue::new(); + + let mut handles = ~[]; + let mut scheds = ~[]; + + for uint::range(0, N) |_| { + let loop_ = ~UvEventLoop::new(); + let mut sched = ~Scheduler::new(loop_, + work_queue.clone(), + sleepers.clone()); + let handle = sched.make_handle(); + rtdebug!("sched id: %u", handle.sched_id); + handles.push(handle); + scheds.push(sched); + }; + + let handles = Cell(handles); + + let home_handle = scheds[6].make_handle(); + let home_id = home_handle.sched_id; + let home = Sched(home_handle); + + let main_task = ~do Coroutine::new_homed(&mut scheds[1].stack_pool, home) { + + // Here we check if the task is running on its home. + let sched = Local::take::(); + rtdebug!("run location scheduler id: %u, home: %u", + sched.sched_id(), + home_id); + assert!(sched.sched_id() == home_id); + Local::put::(sched); + + let mut handles = handles.take(); + for handles.each_mut |handle| { + handle.send(Shutdown); + } + }; + + scheds[0].enqueue_task(main_task); + + let mut threads = ~[]; + + while !scheds.is_empty() { + let sched = scheds.pop(); + let sched_cell = Cell(sched); + let thread = do Thread::start { + let sched = sched_cell.take(); + sched.run(); + }; + threads.push(thread); + } + + let _threads = threads; + } + } + + // The goal is that this is the high-stress test for making sure + // homing is working. It allocates 120*RUST_RT_STRESS tasks that + // do nothing but assert that they are home at execution + // time. These tasks are queued to random schedulers, so sometimes + // they are home and sometimes not. It also runs RUST_RT_STRESS + // times. + + #[test] + fn test_stress_homed_tasks() { + let n = stress_factor(); + for int::range(0,n as int) |_| { + run_in_mt_newsched_task_random_homed(); + } + } + #[test] fn test_simple_scheduling() { @@ -877,4 +1206,5 @@ mod test { } } } + } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index c8df3a612033..97aa76d7db69 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -88,6 +88,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let loop_ = ~UvEventLoop::new(); let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); let handle = sched.make_handle(); + handles.push(handle); scheds.push(sched); } @@ -128,6 +129,96 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { } } +// THIS IS AWFUL. Copy-pasted the above initialization function but +// with a number of hacks to make it spawn tasks on a variety of +// schedulers with a variety of homes using the new spawn. + +pub fn run_in_mt_newsched_task_random_homed() { + use libc; + use os; + use from_str::FromStr; + use rt::uv::uvio::UvEventLoop; + use rt::sched::Shutdown; + + do run_in_bare_thread { + let nthreads = match os::getenv("RUST_TEST_THREADS") { + Some(nstr) => FromStr::from_str(nstr).get(), + None => unsafe { + // Using more threads than cores in test code to force + // the OS to preempt them frequently. Assuming that + // this help stress test concurrent types. + rust_get_num_cpus() * 2 + } + }; + + let sleepers = SleeperList::new(); + let work_queue = WorkQueue::new(); + + let mut handles = ~[]; + let mut scheds = ~[]; + + for uint::range(0, nthreads) |_| { + let loop_ = ~UvEventLoop::new(); + let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); + let handle = sched.make_handle(); + handles.push(handle); + scheds.push(sched); + } + + // Schedule a pile o tasks + let n = 120*stress_factor(); + for uint::range(0,n) |_i| { + rtdebug!("creating task: %u", _i); + let hf: ~fn() = || { assert!(true) }; + spawntask_homed(&mut scheds, hf); + } + + let f: ~fn() = || { assert!(true); }; + + let f_cell = Cell(f); + let handles = Cell(handles); + + rtdebug!("creating main task"); + + let main_task = ~do Coroutine::new(&mut scheds[0].stack_pool) { + f_cell.take()(); + let mut handles = handles.take(); + // Tell schedulers to exit + for handles.each_mut |handle| { + handle.send(Shutdown); + } + }; + + rtdebug!("queuing main task") + + scheds[0].enqueue_task(main_task); + + let mut threads = ~[]; + + while !scheds.is_empty() { + let sched = scheds.pop(); + let sched_cell = Cell(sched); + let thread = do Thread::start { + let sched = sched_cell.take(); + rtdebug!("running sched: %u", sched.sched_id()); + sched.run(); + }; + + threads.push(thread); + } + + rtdebug!("waiting on scheduler threads"); + + // Wait for schedulers + let _threads = threads; + } + + extern { + fn rust_get_num_cpus() -> libc::uintptr_t; + } +} + + /// Test tasks will abort on failure instead of unwinding pub fn spawntask(f: ~fn()) { use super::sched::*; @@ -188,6 +279,38 @@ pub fn spawntask_random(f: ~fn()) { } } +/// Spawn a task, with the current scheduler as home, and queue it to +/// run later. +pub fn spawntask_homed(scheds: &mut ~[~Scheduler], f: ~fn()) { + use super::sched::*; + use rand::{rng, RngUtil}; + let mut rng = rng(); + + let task = { + let sched = &mut scheds[rng.gen_int_range(0,scheds.len() as int)]; + let handle = sched.make_handle(); + let home_id = handle.sched_id; + + // now that we know where this is going, build a new function + // that can assert it is in the right place + let af: ~fn() = || { + do Local::borrow::() |sched| { + rtdebug!("home_id: %u, runtime loc: %u", + home_id, + sched.sched_id()); + assert!(home_id == sched.sched_id()); + }; + f() + }; + + ~Coroutine::with_task_homed(&mut sched.stack_pool, + ~Task::without_unwinding(), + af, + Sched(handle)) + }; + let dest_sched = &mut scheds[rng.gen_int_range(0,scheds.len() as int)]; + dest_sched.enqueue_task(task); +} /// Spawn a task and wait for it to finish, returning whether it completed successfully or failed pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { @@ -266,3 +389,4 @@ pub fn stress_factor() -> uint { } } + From e7213aa21e9a79db01d2e9d1b76761a420e4c967 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 12 Jun 2013 12:00:46 -0700 Subject: [PATCH 36/55] std::rt: Remove old files --- src/libstd/rt/uvio.rs | 459 ------------------------------------------ src/libstd/rt/uvll.rs | 443 ---------------------------------------- 2 files changed, 902 deletions(-) delete mode 100644 src/libstd/rt/uvio.rs delete mode 100644 src/libstd/rt/uvll.rs diff --git a/src/libstd/rt/uvio.rs b/src/libstd/rt/uvio.rs deleted file mode 100644 index 24bffd8d1cd2..000000000000 --- a/src/libstd/rt/uvio.rs +++ /dev/null @@ -1,459 +0,0 @@ -// Copyright 2013 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -use option::*; -use result::*; - -use super::io::net::ip::IpAddr; -use super::uv::*; -use super::rtio::*; -use ops::Drop; -use old_iter::CopyableIter; -use cell::{Cell, empty_cell}; -use cast::transmute; -use super::sched::{Scheduler, local_sched}; - -#[cfg(test)] use container::Container; -#[cfg(test)] use uint; -#[cfg(test)] use unstable::run_in_bare_thread; -#[cfg(test)] use super::test::*; - -pub struct UvEventLoop { - uvio: UvIoFactory -} - -pub impl UvEventLoop { - fn new() -> UvEventLoop { - UvEventLoop { - uvio: UvIoFactory(Loop::new()) - } - } - - /// A convenience constructor - fn new_scheduler() -> Scheduler { - Scheduler::new(~UvEventLoop::new()) - } -} - -impl Drop for UvEventLoop { - fn finalize(&self) { - // XXX: Need mutable finalizer - let this = unsafe { - transmute::<&UvEventLoop, &mut UvEventLoop>(self) - }; - this.uvio.uv_loop().close(); - } -} - -impl EventLoop for UvEventLoop { - - fn run(&mut self) { - self.uvio.uv_loop().run(); - } - - fn callback(&mut self, f: ~fn()) { - let mut idle_watcher = IdleWatcher::new(self.uvio.uv_loop()); - do idle_watcher.start |idle_watcher, status| { - assert!(status.is_none()); - let mut idle_watcher = idle_watcher; - idle_watcher.stop(); - idle_watcher.close(); - f(); - } - } - - fn io<'a>(&'a mut self) -> Option<&'a mut IoFactoryObject> { - Some(&mut self.uvio) - } -} - -#[test] -fn test_callback_run_once() { - do run_in_bare_thread { - let mut event_loop = UvEventLoop::new(); - let mut count = 0; - let count_ptr: *mut int = &mut count; - do event_loop.callback { - unsafe { *count_ptr += 1 } - } - event_loop.run(); - assert!(count == 1); - } -} - -pub struct UvIoFactory(Loop); - -pub impl UvIoFactory { - fn uv_loop<'a>(&'a mut self) -> &'a mut Loop { - match self { &UvIoFactory(ref mut ptr) => ptr } - } -} - -impl IoFactory for UvIoFactory { - // Connect to an address and return a new stream - // NB: This blocks the task waiting on the connection. - // It would probably be better to return a future - fn connect(&mut self, addr: IpAddr) -> Option<~StreamObject> { - // Create a cell in the task to hold the result. We will fill - // the cell before resuming the task. - let result_cell = empty_cell(); - let result_cell_ptr: *Cell> = &result_cell; - - let scheduler = local_sched::take(); - assert!(scheduler.in_task_context()); - - // Block this task and take ownership, switch to scheduler context - do scheduler.deschedule_running_task_and_then |task| { - - rtdebug!("connect: entered scheduler context"); - do local_sched::borrow |scheduler| { - assert!(!scheduler.in_task_context()); - } - let mut tcp_watcher = TcpWatcher::new(self.uv_loop()); - let task_cell = Cell(task); - - // Wait for a connection - do tcp_watcher.connect(addr) |stream_watcher, status| { - rtdebug!("connect: in connect callback"); - let maybe_stream = if status.is_none() { - rtdebug!("status is none"); - Some(~UvStream(stream_watcher)) - } else { - rtdebug!("status is some"); - stream_watcher.close(||()); - None - }; - - // Store the stream in the task's stack - unsafe { (*result_cell_ptr).put_back(maybe_stream); } - - // Context switch - let scheduler = local_sched::take(); - scheduler.resume_task_immediately(task_cell.take()); - } - } - - assert!(!result_cell.is_empty()); - return result_cell.take(); - } - - fn bind(&mut self, addr: IpAddr) -> Option<~TcpListenerObject> { - let mut watcher = TcpWatcher::new(self.uv_loop()); - watcher.bind(addr); - return Some(~UvTcpListener(watcher)); - } -} - -pub struct UvTcpListener(TcpWatcher); - -impl UvTcpListener { - fn watcher(&self) -> TcpWatcher { - match self { &UvTcpListener(w) => w } - } - - fn close(&self) { - // XXX: Need to wait until close finishes before returning - self.watcher().as_stream().close(||()); - } -} - -impl Drop for UvTcpListener { - fn finalize(&self) { - // XXX: Again, this never gets called. Use .close() instead - //self.watcher().as_stream().close(||()); - } -} - -impl TcpListener for UvTcpListener { - - fn listen(&mut self) -> Option<~StreamObject> { - rtdebug!("entering listen"); - let result_cell = empty_cell(); - let result_cell_ptr: *Cell> = &result_cell; - - let server_tcp_watcher = self.watcher(); - - let scheduler = local_sched::take(); - assert!(scheduler.in_task_context()); - - do scheduler.deschedule_running_task_and_then |task| { - let task_cell = Cell(task); - let mut server_tcp_watcher = server_tcp_watcher; - do server_tcp_watcher.listen |server_stream_watcher, status| { - let maybe_stream = if status.is_none() { - let mut server_stream_watcher = server_stream_watcher; - let mut loop_ = loop_from_watcher(&server_stream_watcher); - let client_tcp_watcher = TcpWatcher::new(&mut loop_).as_stream(); - // XXX: Needs to be surfaced in interface - server_stream_watcher.accept(client_tcp_watcher); - Some(~UvStream::new(client_tcp_watcher)) - } else { - None - }; - - unsafe { (*result_cell_ptr).put_back(maybe_stream); } - - rtdebug!("resuming task from listen"); - // Context switch - let scheduler = local_sched::take(); - scheduler.resume_task_immediately(task_cell.take()); - } - } - - assert!(!result_cell.is_empty()); - return result_cell.take(); - } -} - -pub struct UvStream(StreamWatcher); - -impl UvStream { - fn new(watcher: StreamWatcher) -> UvStream { - UvStream(watcher) - } - - fn watcher(&self) -> StreamWatcher { - match self { &UvStream(w) => w } - } - - // XXX: finalize isn't working for ~UvStream??? - fn close(&self) { - // XXX: Need to wait until this finishes before returning - self.watcher().close(||()); - } -} - -impl Drop for UvStream { - fn finalize(&self) { - rtdebug!("closing stream"); - //self.watcher().close(||()); - } -} - -impl Stream for UvStream { - fn read(&mut self, buf: &mut [u8]) -> Result { - let result_cell = empty_cell(); - let result_cell_ptr: *Cell> = &result_cell; - - let scheduler = local_sched::take(); - assert!(scheduler.in_task_context()); - let watcher = self.watcher(); - let buf_ptr: *&mut [u8] = &buf; - do scheduler.deschedule_running_task_and_then |task| { - rtdebug!("read: entered scheduler context"); - do local_sched::borrow |scheduler| { - assert!(!scheduler.in_task_context()); - } - let mut watcher = watcher; - let task_cell = Cell(task); - // XXX: We shouldn't reallocate these callbacks every - // call to read - let alloc: AllocCallback = |_| unsafe { - slice_to_uv_buf(*buf_ptr) - }; - do watcher.read_start(alloc) |watcher, nread, _buf, status| { - - // Stop reading so that no read callbacks are - // triggered before the user calls `read` again. - // XXX: Is there a performance impact to calling - // stop here? - let mut watcher = watcher; - watcher.read_stop(); - - let result = if status.is_none() { - assert!(nread >= 0); - Ok(nread as uint) - } else { - Err(()) - }; - - unsafe { (*result_cell_ptr).put_back(result); } - - let scheduler = local_sched::take(); - scheduler.resume_task_immediately(task_cell.take()); - } - } - - assert!(!result_cell.is_empty()); - return result_cell.take(); - } - - fn write(&mut self, buf: &[u8]) -> Result<(), ()> { - let result_cell = empty_cell(); - let result_cell_ptr: *Cell> = &result_cell; - let scheduler = local_sched::take(); - assert!(scheduler.in_task_context()); - let watcher = self.watcher(); - let buf_ptr: *&[u8] = &buf; - do scheduler.deschedule_running_task_and_then |task| { - let mut watcher = watcher; - let task_cell = Cell(task); - let buf = unsafe { &*buf_ptr }; - // XXX: OMGCOPIES - let buf = buf.to_vec(); - do watcher.write(buf) |_watcher, status| { - let result = if status.is_none() { - Ok(()) - } else { - Err(()) - }; - - unsafe { (*result_cell_ptr).put_back(result); } - - let scheduler = local_sched::take(); - scheduler.resume_task_immediately(task_cell.take()); - } - } - - assert!(!result_cell.is_empty()); - return result_cell.take(); - } -} - -#[test] -fn test_simple_io_no_connect() { - do run_in_newsched_task { - let io = unsafe { local_sched::unsafe_borrow_io() }; - let addr = next_test_ip4(); - let maybe_chan = io.connect(addr); - assert!(maybe_chan.is_none()); - } -} - -#[test] -fn test_simple_tcp_server_and_client() { - do run_in_newsched_task { - let addr = next_test_ip4(); - - // Start the server first so it's listening when we connect - do spawntask_immediately { - unsafe { - let io = local_sched::unsafe_borrow_io(); - let mut listener = io.bind(addr).unwrap(); - let mut stream = listener.listen().unwrap(); - let mut buf = [0, .. 2048]; - let nread = stream.read(buf).unwrap(); - assert!(nread == 8); - for uint::range(0, nread) |i| { - rtdebug!("%u", buf[i] as uint); - assert!(buf[i] == i as u8); - } - stream.close(); - listener.close(); - } - } - - do spawntask_immediately { - unsafe { - let io = local_sched::unsafe_borrow_io(); - let mut stream = io.connect(addr).unwrap(); - stream.write([0, 1, 2, 3, 4, 5, 6, 7]); - stream.close(); - } - } - } -} - -#[test] #[ignore(reason = "busted")] -fn test_read_and_block() { - do run_in_newsched_task { - let addr = next_test_ip4(); - - do spawntask_immediately { - let io = unsafe { local_sched::unsafe_borrow_io() }; - let mut listener = io.bind(addr).unwrap(); - let mut stream = listener.listen().unwrap(); - let mut buf = [0, .. 2048]; - - let expected = 32; - let mut current = 0; - let mut reads = 0; - - while current < expected { - let nread = stream.read(buf).unwrap(); - for uint::range(0, nread) |i| { - let val = buf[i] as uint; - assert!(val == current % 8); - current += 1; - } - reads += 1; - - let scheduler = local_sched::take(); - // Yield to the other task in hopes that it - // will trigger a read callback while we are - // not ready for it - do scheduler.deschedule_running_task_and_then |task| { - let task = Cell(task); - do local_sched::borrow |scheduler| { - scheduler.task_queue.push_back(task.take()); - } - } - } - - // Make sure we had multiple reads - assert!(reads > 1); - - stream.close(); - listener.close(); - } - - do spawntask_immediately { - let io = unsafe { local_sched::unsafe_borrow_io() }; - let mut stream = io.connect(addr).unwrap(); - stream.write([0, 1, 2, 3, 4, 5, 6, 7]); - stream.write([0, 1, 2, 3, 4, 5, 6, 7]); - stream.write([0, 1, 2, 3, 4, 5, 6, 7]); - stream.write([0, 1, 2, 3, 4, 5, 6, 7]); - stream.close(); - } - - } -} - -#[test] -fn test_read_read_read() { - do run_in_newsched_task { - let addr = next_test_ip4(); - static MAX: uint = 500000; - - do spawntask_immediately { - unsafe { - let io = local_sched::unsafe_borrow_io(); - let mut listener = io.bind(addr).unwrap(); - let mut stream = listener.listen().unwrap(); - let buf = [1, .. 2048]; - let mut total_bytes_written = 0; - while total_bytes_written < MAX { - stream.write(buf); - total_bytes_written += buf.len(); - } - stream.close(); - listener.close(); - } - } - - do spawntask_immediately { - let io = unsafe { local_sched::unsafe_borrow_io() }; - let mut stream = io.connect(addr).unwrap(); - let mut buf = [0, .. 2048]; - let mut total_bytes_read = 0; - while total_bytes_read < MAX { - let nread = stream.read(buf).unwrap(); - rtdebug!("read %u bytes", nread as uint); - total_bytes_read += nread; - for uint::range(0, nread) |i| { - assert!(buf[i] == 1); - } - } - rtdebug!("read %u bytes total", total_bytes_read as uint); - stream.close(); - } - } -} diff --git a/src/libstd/rt/uvll.rs b/src/libstd/rt/uvll.rs deleted file mode 100644 index 0d298bde6b50..000000000000 --- a/src/libstd/rt/uvll.rs +++ /dev/null @@ -1,443 +0,0 @@ -// Copyright 2012 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 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -/*! - * Low-level bindings to the libuv library. - * - * This module contains a set of direct, 'bare-metal' wrappers around - * the libuv C-API. - * - * We're not bothering yet to redefine uv's structs as Rust structs - * because they are quite large and change often between versions. - * The maintenance burden is just too high. Instead we use the uv's - * `uv_handle_size` and `uv_req_size` to find the correct size of the - * structs and allocate them on the heap. This can be revisited later. - * - * There are also a collection of helper functions to ease interacting - * with the low-level API. - * - * As new functionality, existant in uv.h, is added to the rust stdlib, - * the mappings should be added in this module. - */ - -#[allow(non_camel_case_types)]; // C types - -use libc::{size_t, c_int, c_uint, c_void, c_char, uintptr_t}; -use libc::{malloc, free}; -use prelude::*; - -pub struct uv_err_t { - code: c_int, - sys_errno_: c_int -} - -pub struct uv_buf_t { - base: *u8, - len: libc::size_t, -} - -pub type uv_handle_t = c_void; -pub type uv_loop_t = c_void; -pub type uv_idle_t = c_void; -pub type uv_tcp_t = c_void; -pub type uv_connect_t = c_void; -pub type uv_write_t = c_void; -pub type uv_async_t = c_void; -pub type uv_timer_t = c_void; -pub type uv_stream_t = c_void; -pub type uv_fs_t = c_void; - -pub type uv_idle_cb = *u8; - -pub type sockaddr_in = c_void; -pub type sockaddr_in6 = c_void; - -#[deriving(Eq)] -pub enum uv_handle_type { - UV_UNKNOWN_HANDLE, - UV_ASYNC, - UV_CHECK, - UV_FS_EVENT, - UV_FS_POLL, - UV_HANDLE, - UV_IDLE, - UV_NAMED_PIPE, - UV_POLL, - UV_PREPARE, - UV_PROCESS, - UV_STREAM, - UV_TCP, - UV_TIMER, - UV_TTY, - UV_UDP, - UV_SIGNAL, - UV_FILE, - UV_HANDLE_TYPE_MAX -} - -#[deriving(Eq)] -pub enum uv_req_type { - UV_UNKNOWN_REQ, - UV_REQ, - UV_CONNECT, - UV_WRITE, - UV_SHUTDOWN, - UV_UDP_SEND, - UV_FS, - UV_WORK, - UV_GETADDRINFO, - UV_REQ_TYPE_MAX -} - -pub unsafe fn malloc_handle(handle: uv_handle_type) -> *c_void { - assert!(handle != UV_UNKNOWN_HANDLE && handle != UV_HANDLE_TYPE_MAX); - let size = rust_uv_handle_size(handle as uint); - let p = malloc(size); - assert!(p.is_not_null()); - return p; -} - -pub unsafe fn free_handle(v: *c_void) { - free(v) -} - -pub unsafe fn malloc_req(req: uv_req_type) -> *c_void { - assert!(req != UV_UNKNOWN_REQ && req != UV_REQ_TYPE_MAX); - let size = rust_uv_req_size(req as uint); - let p = malloc(size); - assert!(p.is_not_null()); - return p; -} - -pub unsafe fn free_req(v: *c_void) { - free(v) -} - -#[test] -fn handle_sanity_check() { - unsafe { - assert!(UV_HANDLE_TYPE_MAX as uint == rust_uv_handle_type_max()); - } -} - -#[test] -fn request_sanity_check() { - unsafe { - assert!(UV_REQ_TYPE_MAX as uint == rust_uv_req_type_max()); - } -} - -pub unsafe fn loop_new() -> *c_void { - return rust_uv_loop_new(); -} - -pub unsafe fn loop_delete(loop_handle: *c_void) { - rust_uv_loop_delete(loop_handle); -} - -pub unsafe fn run(loop_handle: *c_void) { - rust_uv_run(loop_handle); -} - -pub unsafe fn close(handle: *T, cb: *u8) { - rust_uv_close(handle as *c_void, cb); -} - -pub unsafe fn walk(loop_handle: *c_void, cb: *u8, arg: *c_void) { - rust_uv_walk(loop_handle, cb, arg); -} - -pub unsafe fn idle_new() -> *uv_idle_t { - rust_uv_idle_new() -} - -pub unsafe fn idle_delete(handle: *uv_idle_t) { - rust_uv_idle_delete(handle) -} - -pub unsafe fn idle_init(loop_handle: *uv_loop_t, handle: *uv_idle_t) -> c_int { - rust_uv_idle_init(loop_handle, handle) -} - -pub unsafe fn idle_start(handle: *uv_idle_t, cb: uv_idle_cb) -> c_int { - rust_uv_idle_start(handle, cb) -} - -pub unsafe fn idle_stop(handle: *uv_idle_t) -> c_int { - rust_uv_idle_stop(handle) -} - -pub unsafe fn tcp_init(loop_handle: *c_void, handle: *uv_tcp_t) -> c_int { - return rust_uv_tcp_init(loop_handle, handle); -} - -// FIXME ref #2064 -pub unsafe fn tcp_connect(connect_ptr: *uv_connect_t, - tcp_handle_ptr: *uv_tcp_t, - addr_ptr: *sockaddr_in, - after_connect_cb: *u8) -> c_int { - return rust_uv_tcp_connect(connect_ptr, tcp_handle_ptr, - after_connect_cb, addr_ptr); -} -// FIXME ref #2064 -pub unsafe fn tcp_connect6(connect_ptr: *uv_connect_t, - tcp_handle_ptr: *uv_tcp_t, - addr_ptr: *sockaddr_in6, - after_connect_cb: *u8) -> c_int { - return rust_uv_tcp_connect6(connect_ptr, tcp_handle_ptr, - after_connect_cb, addr_ptr); -} -// FIXME ref #2064 -pub unsafe fn tcp_bind(tcp_server_ptr: *uv_tcp_t, addr_ptr: *sockaddr_in) -> c_int { - return rust_uv_tcp_bind(tcp_server_ptr, addr_ptr); -} -// FIXME ref #2064 -pub unsafe fn tcp_bind6(tcp_server_ptr: *uv_tcp_t, addr_ptr: *sockaddr_in6) -> c_int { - return rust_uv_tcp_bind6(tcp_server_ptr, addr_ptr); -} - -pub unsafe fn tcp_getpeername(tcp_handle_ptr: *uv_tcp_t, name: *sockaddr_in) -> c_int { - return rust_uv_tcp_getpeername(tcp_handle_ptr, name); -} - -pub unsafe fn tcp_getpeername6(tcp_handle_ptr: *uv_tcp_t, name: *sockaddr_in6) ->c_int { - return rust_uv_tcp_getpeername6(tcp_handle_ptr, name); -} - -pub unsafe fn listen(stream: *T, backlog: c_int, cb: *u8) -> c_int { - return rust_uv_listen(stream as *c_void, backlog, cb); -} - -pub unsafe fn accept(server: *c_void, client: *c_void) -> c_int { - return rust_uv_accept(server as *c_void, client as *c_void); -} - -pub unsafe fn write(req: *uv_write_t, stream: *T, buf_in: &[uv_buf_t], cb: *u8) -> c_int { - let buf_ptr = vec::raw::to_ptr(buf_in); - let buf_cnt = buf_in.len() as i32; - return rust_uv_write(req as *c_void, stream as *c_void, buf_ptr, buf_cnt, cb); -} -pub unsafe fn read_start(stream: *uv_stream_t, on_alloc: *u8, on_read: *u8) -> c_int { - return rust_uv_read_start(stream as *c_void, on_alloc, on_read); -} - -pub unsafe fn read_stop(stream: *uv_stream_t) -> c_int { - return rust_uv_read_stop(stream as *c_void); -} - -pub unsafe fn last_error(loop_handle: *c_void) -> uv_err_t { - return rust_uv_last_error(loop_handle); -} - -pub unsafe fn strerror(err: *uv_err_t) -> *c_char { - return rust_uv_strerror(err); -} -pub unsafe fn err_name(err: *uv_err_t) -> *c_char { - return rust_uv_err_name(err); -} - -pub unsafe fn async_init(loop_handle: *c_void, async_handle: *uv_async_t, cb: *u8) -> c_int { - return rust_uv_async_init(loop_handle, async_handle, cb); -} - -pub unsafe fn async_send(async_handle: *uv_async_t) { - return rust_uv_async_send(async_handle); -} -pub unsafe fn buf_init(input: *u8, len: uint) -> uv_buf_t { - let out_buf = uv_buf_t { base: ptr::null(), len: 0 as size_t }; - let out_buf_ptr = ptr::to_unsafe_ptr(&out_buf); - rust_uv_buf_init(out_buf_ptr, input, len as size_t); - return out_buf; -} - -pub unsafe fn timer_init(loop_ptr: *c_void, timer_ptr: *uv_timer_t) -> c_int { - return rust_uv_timer_init(loop_ptr, timer_ptr); -} -pub unsafe fn timer_start(timer_ptr: *uv_timer_t, cb: *u8, timeout: uint, - repeat: uint) -> c_int { - return rust_uv_timer_start(timer_ptr, cb, timeout as c_uint, repeat as c_uint); -} -pub unsafe fn timer_stop(timer_ptr: *uv_timer_t) -> c_int { - return rust_uv_timer_stop(timer_ptr); -} - -pub unsafe fn malloc_ip4_addr(ip: &str, port: int) -> *sockaddr_in { - do str::as_c_str(ip) |ip_buf| { - rust_uv_ip4_addrp(ip_buf as *u8, port as libc::c_int) - } -} -pub unsafe fn malloc_ip6_addr(ip: &str, port: int) -> *sockaddr_in6 { - do str::as_c_str(ip) |ip_buf| { - rust_uv_ip6_addrp(ip_buf as *u8, port as libc::c_int) - } -} - -pub unsafe fn free_ip4_addr(addr: *sockaddr_in) { - rust_uv_free_ip4_addr(addr); -} - -pub unsafe fn free_ip6_addr(addr: *sockaddr_in6) { - rust_uv_free_ip6_addr(addr); -} - -// data access helpers -pub unsafe fn get_loop_for_uv_handle(handle: *T) -> *c_void { - return rust_uv_get_loop_for_uv_handle(handle as *c_void); -} -pub unsafe fn get_stream_handle_from_connect_req(connect: *uv_connect_t) -> *uv_stream_t { - return rust_uv_get_stream_handle_from_connect_req(connect); -} -pub unsafe fn get_stream_handle_from_write_req(write_req: *uv_write_t) -> *uv_stream_t { - return rust_uv_get_stream_handle_from_write_req(write_req); -} -pub unsafe fn get_data_for_uv_loop(loop_ptr: *c_void) -> *c_void { - rust_uv_get_data_for_uv_loop(loop_ptr) -} -pub unsafe fn set_data_for_uv_loop(loop_ptr: *c_void, data: *c_void) { - rust_uv_set_data_for_uv_loop(loop_ptr, data); -} -pub unsafe fn get_data_for_uv_handle(handle: *T) -> *c_void { - return rust_uv_get_data_for_uv_handle(handle as *c_void); -} -pub unsafe fn set_data_for_uv_handle(handle: *T, data: *U) { - rust_uv_set_data_for_uv_handle(handle as *c_void, data as *c_void); -} -pub unsafe fn get_data_for_req(req: *T) -> *c_void { - return rust_uv_get_data_for_req(req as *c_void); -} -pub unsafe fn set_data_for_req(req: *T, data: *U) { - rust_uv_set_data_for_req(req as *c_void, data as *c_void); -} -pub unsafe fn get_base_from_buf(buf: uv_buf_t) -> *u8 { - return rust_uv_get_base_from_buf(buf); -} -pub unsafe fn get_len_from_buf(buf: uv_buf_t) -> size_t { - return rust_uv_get_len_from_buf(buf); -} -pub unsafe fn malloc_buf_base_of(suggested_size: size_t) -> *u8 { - return rust_uv_malloc_buf_base_of(suggested_size); -} -pub unsafe fn free_base_of_buf(buf: uv_buf_t) { - rust_uv_free_base_of_buf(buf); -} - -pub unsafe fn get_last_err_info(uv_loop: *c_void) -> ~str { - let err = last_error(uv_loop); - let err_ptr = ptr::to_unsafe_ptr(&err); - let err_name = str::raw::from_c_str(err_name(err_ptr)); - let err_msg = str::raw::from_c_str(strerror(err_ptr)); - return fmt!("LIBUV ERROR: name: %s msg: %s", - err_name, err_msg); -} - -pub unsafe fn get_last_err_data(uv_loop: *c_void) -> uv_err_data { - let err = last_error(uv_loop); - let err_ptr = ptr::to_unsafe_ptr(&err); - let err_name = str::raw::from_c_str(err_name(err_ptr)); - let err_msg = str::raw::from_c_str(strerror(err_ptr)); - uv_err_data { err_name: err_name, err_msg: err_msg } -} - -pub struct uv_err_data { - err_name: ~str, - err_msg: ~str, -} - -extern { - - fn rust_uv_handle_size(type_: uintptr_t) -> size_t; - fn rust_uv_req_size(type_: uintptr_t) -> size_t; - fn rust_uv_handle_type_max() -> uintptr_t; - fn rust_uv_req_type_max() -> uintptr_t; - - // libuv public API - fn rust_uv_loop_new() -> *c_void; - fn rust_uv_loop_delete(lp: *c_void); - fn rust_uv_run(loop_handle: *c_void); - fn rust_uv_close(handle: *c_void, cb: *u8); - fn rust_uv_walk(loop_handle: *c_void, cb: *u8, arg: *c_void); - - fn rust_uv_idle_new() -> *uv_idle_t; - fn rust_uv_idle_delete(handle: *uv_idle_t); - fn rust_uv_idle_init(loop_handle: *uv_loop_t, handle: *uv_idle_t) -> c_int; - fn rust_uv_idle_start(handle: *uv_idle_t, cb: uv_idle_cb) -> c_int; - fn rust_uv_idle_stop(handle: *uv_idle_t) -> c_int; - - fn rust_uv_async_send(handle: *uv_async_t); - fn rust_uv_async_init(loop_handle: *c_void, - async_handle: *uv_async_t, - cb: *u8) -> c_int; - fn rust_uv_tcp_init(loop_handle: *c_void, handle_ptr: *uv_tcp_t) -> c_int; - // FIXME ref #2604 .. ? - fn rust_uv_buf_init(out_buf: *uv_buf_t, base: *u8, len: size_t); - fn rust_uv_last_error(loop_handle: *c_void) -> uv_err_t; - // FIXME ref #2064 - fn rust_uv_strerror(err: *uv_err_t) -> *c_char; - // FIXME ref #2064 - fn rust_uv_err_name(err: *uv_err_t) -> *c_char; - fn rust_uv_ip4_addrp(ip: *u8, port: c_int) -> *sockaddr_in; - fn rust_uv_ip6_addrp(ip: *u8, port: c_int) -> *sockaddr_in6; - fn rust_uv_free_ip4_addr(addr: *sockaddr_in); - fn rust_uv_free_ip6_addr(addr: *sockaddr_in6); - fn rust_uv_ip4_name(src: *sockaddr_in, dst: *u8, size: size_t) -> c_int; - fn rust_uv_ip6_name(src: *sockaddr_in6, dst: *u8, size: size_t) -> c_int; - fn rust_uv_ip4_port(src: *sockaddr_in) -> c_uint; - fn rust_uv_ip6_port(src: *sockaddr_in6) -> c_uint; - // FIXME ref #2064 - fn rust_uv_tcp_connect(connect_ptr: *uv_connect_t, - tcp_handle_ptr: *uv_tcp_t, - after_cb: *u8, - addr: *sockaddr_in) -> c_int; - // FIXME ref #2064 - fn rust_uv_tcp_bind(tcp_server: *uv_tcp_t, addr: *sockaddr_in) -> c_int; - // FIXME ref #2064 - fn rust_uv_tcp_connect6(connect_ptr: *uv_connect_t, - tcp_handle_ptr: *uv_tcp_t, - after_cb: *u8, - addr: *sockaddr_in6) -> c_int; - // FIXME ref #2064 - fn rust_uv_tcp_bind6(tcp_server: *uv_tcp_t, addr: *sockaddr_in6) -> c_int; - fn rust_uv_tcp_getpeername(tcp_handle_ptr: *uv_tcp_t, - name: *sockaddr_in) -> c_int; - fn rust_uv_tcp_getpeername6(tcp_handle_ptr: *uv_tcp_t, - name: *sockaddr_in6) ->c_int; - fn rust_uv_listen(stream: *c_void, backlog: c_int, cb: *u8) -> c_int; - fn rust_uv_accept(server: *c_void, client: *c_void) -> c_int; - fn rust_uv_write(req: *c_void, - stream: *c_void, - buf_in: *uv_buf_t, - buf_cnt: c_int, - cb: *u8) -> c_int; - fn rust_uv_read_start(stream: *c_void, - on_alloc: *u8, - on_read: *u8) -> c_int; - fn rust_uv_read_stop(stream: *c_void) -> c_int; - fn rust_uv_timer_init(loop_handle: *c_void, - timer_handle: *uv_timer_t) -> c_int; - fn rust_uv_timer_start(timer_handle: *uv_timer_t, - cb: *u8, - timeout: c_uint, - repeat: c_uint) -> c_int; - fn rust_uv_timer_stop(handle: *uv_timer_t) -> c_int; - - fn rust_uv_malloc_buf_base_of(sug_size: size_t) -> *u8; - fn rust_uv_free_base_of_buf(buf: uv_buf_t); - fn rust_uv_get_stream_handle_from_connect_req(connect_req: *uv_connect_t) -> *uv_stream_t; - fn rust_uv_get_stream_handle_from_write_req(write_req: *uv_write_t) -> *uv_stream_t; - fn rust_uv_get_loop_for_uv_handle(handle: *c_void) -> *c_void; - fn rust_uv_get_data_for_uv_loop(loop_ptr: *c_void) -> *c_void; - fn rust_uv_set_data_for_uv_loop(loop_ptr: *c_void, data: *c_void); - fn rust_uv_get_data_for_uv_handle(handle: *c_void) -> *c_void; - fn rust_uv_set_data_for_uv_handle(handle: *c_void, data: *c_void); - fn rust_uv_get_data_for_req(req: *c_void) -> *c_void; - fn rust_uv_set_data_for_req(req: *c_void, data: *c_void); - fn rust_uv_get_base_from_buf(buf: uv_buf_t) -> *u8; - fn rust_uv_get_len_from_buf(buf: uv_buf_t) -> size_t; -} From 4224fc7aad3cfbd7093e55812e5a566d7aad3325 Mon Sep 17 00:00:00 2001 From: toddaaro Date: Wed, 12 Jun 2013 14:55:32 -0700 Subject: [PATCH 37/55] added functionality to tell schedulers to refuse to run tasks that are not pinned to them --- src/libstd/rt/sched.rs | 48 ++++++++++++++++++++++++++++++------------ src/libstd/rt/test.rs | 30 +++++++++++++++++++++----- 2 files changed, 60 insertions(+), 18 deletions(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 698cafdf8c61..4bc61d638248 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -70,7 +70,9 @@ pub struct Scheduler { /// An action performed after a context switch on behalf of the /// code running before the context switch priv cleanup_job: Option, - metrics: SchedMetrics + metrics: SchedMetrics, + /// Should this scheduler run any task, or only pinned tasks? + run_anything: bool } pub struct SchedHandle { @@ -136,6 +138,16 @@ pub impl Scheduler { sleeper_list: SleeperList) -> Scheduler { + Scheduler::new_special(event_loop, work_queue, sleeper_list, true) + + } + + fn new_special(event_loop: ~EventLoopObject, + work_queue: WorkQueue<~Coroutine>, + sleeper_list: SleeperList, + run_anything: bool) + -> Scheduler { + // Lazily initialize the runtime TLS key local_ptr::init_tls_key(); @@ -150,7 +162,8 @@ pub impl Scheduler { saved_context: Context::empty(), current_task: None, cleanup_job: None, - metrics: SchedMetrics::new() + metrics: SchedMetrics::new(), + run_anything: run_anything } } @@ -429,19 +442,28 @@ pub impl Scheduler { assert!(!self.in_task_context()); rtdebug!("looking in work queue for task to schedule"); - let mut this = self; - match this.work_queue.pop() { - Some(task) => { - rtdebug!("resuming task from work queue"); - this.resume_task_immediately(task); - return true; - } - None => { - rtdebug!("no tasks in queue"); - Local::put(this); - return false; + + if this.run_anything { + match this.work_queue.pop() { + Some(task) => { + rtdebug!("resuming task from work queue"); + this.resume_task_immediately(task); + return true; + } + None => { + rtdebug!("no tasks in queue"); + Local::put(this); + return false; + } } + } else { + // In this branch we have a scheduler that is not allowed + // to run unpinned tasks. As such it will only get tasks + // to run from the message queue. + rtdebug!("skipping resume_task_from_queue"); + Local::put(this); + return false; } } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index 97aa76d7db69..ecef505ce579 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -157,23 +157,43 @@ pub fn run_in_mt_newsched_task_random_homed() { let mut handles = ~[]; let mut scheds = ~[]; - for uint::range(0, nthreads) |_| { + // create a few special schedulers, those with even indicies + // will be pinned-only + for uint::range(0, nthreads) |i| { + let special = (i % 2) == 0; let loop_ = ~UvEventLoop::new(); - let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); + let mut sched = ~Scheduler::new_special(loop_, work_queue.clone(), sleepers.clone(), special); let handle = sched.make_handle(); handles.push(handle); scheds.push(sched); - } + } // Schedule a pile o tasks - let n = 120*stress_factor(); + let n = 5*stress_factor(); for uint::range(0,n) |_i| { rtdebug!("creating task: %u", _i); let hf: ~fn() = || { assert!(true) }; spawntask_homed(&mut scheds, hf); } - let f: ~fn() = || { assert!(true); }; + // Now we want another pile o tasks that do not ever run on a + // special scheduler, because they are normal tasks. Because + // we can we put these in the "main" task. + + let n = 5*stress_factor(); + + let f: ~fn() = || { + for uint::range(0,n) |_| { + let f: ~fn() = || { + // Borrow the scheduler we run on and check if it is + // privliged. + do Local::borrow:: |sched| { + assert!(sched.run_anything); + }; + }; + spawntask_random(f); + }; + }; let f_cell = Cell(f); let handles = Cell(handles); From abc3a8aa1e76f3ecc3930e20453a52681843cec0 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Sun, 2 Jun 2013 01:55:22 -0700 Subject: [PATCH 38/55] std::rt: Add JoinLatch This is supposed to be an efficient way to link the lifetimes of tasks into a tree. JoinLatches form a tree and when `release` is called they wait on children then signal the parent. This structure creates zombie tasks which currently keep the entire task allocated. Zombie tasks are supposed to be tombstoned but that code does not work correctly. --- src/libstd/rt/join_latch.rs | 645 ++++++++++++++++++++++++++++++++++++ src/libstd/rt/metrics.rs | 16 +- src/libstd/rt/mod.rs | 3 + src/libstd/rt/sched.rs | 5 +- 4 files changed, 665 insertions(+), 4 deletions(-) create mode 100644 src/libstd/rt/join_latch.rs diff --git a/src/libstd/rt/join_latch.rs b/src/libstd/rt/join_latch.rs new file mode 100644 index 000000000000..6ffba992fdf4 --- /dev/null +++ b/src/libstd/rt/join_latch.rs @@ -0,0 +1,645 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The JoinLatch is a concurrent type that establishes the task +//! tree and propagates failure. +//! +//! Each task gets a JoinLatch that is derived from the JoinLatch +//! of its parent task. Every latch must be released by either calling +//! the non-blocking `release` method or the task-blocking `wait` method. +//! Releasing a latch does not complete until all of its child latches +//! complete. +//! +//! Latches carry a `success` flag that is set to `false` during task +//! failure and is propagated both from children to parents and parents +//! to children. The status af this flag may be queried for the purposes +//! of linked failure. +//! +//! In addition to failure propagation the task tree serves to keep the +//! default task schedulers alive. The runtime only sends the shutdown +//! message to schedulers once the root task exits. +//! +//! Under this scheme tasks that terminate before their children become +//! 'zombies' since they may not exit until their children do. Zombie +//! tasks are 'tombstoned' as `Tombstone(~JoinLatch)` and the tasks +//! themselves allowed to terminate. +//! +//! XXX: Propagate flag from parents to children. +//! XXX: Tombstoning actually doesn't work. +//! XXX: This could probably be done in a way that doesn't leak tombstones +//! longer than the life of the child tasks. + +use comm::{GenericPort, Peekable, GenericSmartChan}; +use clone::Clone; +use container::Container; +use option::{Option, Some, None}; +use ops::Drop; +use rt::comm::{SharedChan, Port, stream}; +use rt::local::Local; +use rt::sched::Scheduler; +use unstable::atomics::{AtomicUint, SeqCst}; +use util; +use vec::OwnedVector; + +// FIXME #7026: Would prefer this to be an enum +pub struct JoinLatch { + priv parent: Option, + priv child: Option, + closed: bool, +} + +// Shared between parents and all their children. +struct SharedState { + /// Reference count, held by a parent and all children. + count: AtomicUint, + success: bool +} + +struct ParentLink { + shared: *mut SharedState, + // For communicating with the parent. + chan: SharedChan +} + +struct ChildLink { + shared: ~SharedState, + // For receiving from children. + port: Port, + chan: SharedChan, + // Prevents dropping the child SharedState reference counts multiple times. + dropped_child: bool +} + +// Messages from child latches to parent. +enum Message { + Tombstone(~JoinLatch), + ChildrenTerminated +} + +impl JoinLatch { + pub fn new_root() -> ~JoinLatch { + let this = ~JoinLatch { + parent: None, + child: None, + closed: false + }; + rtdebug!("new root latch %x", this.id()); + return this; + } + + fn id(&self) -> uint { + unsafe { ::cast::transmute(&*self) } + } + + pub fn new_child(&mut self) -> ~JoinLatch { + rtassert!(!self.closed); + + if self.child.is_none() { + // This is the first time spawning a child + let shared = ~SharedState { + count: AtomicUint::new(1), + success: true + }; + let (port, chan) = stream(); + let chan = SharedChan::new(chan); + let child = ChildLink { + shared: shared, + port: port, + chan: chan, + dropped_child: false + }; + self.child = Some(child); + } + + let child_link: &mut ChildLink = self.child.get_mut_ref(); + let shared_state: *mut SharedState = &mut *child_link.shared; + + child_link.shared.count.fetch_add(1, SeqCst); + + let child = ~JoinLatch { + parent: Some(ParentLink { + shared: shared_state, + chan: child_link.chan.clone() + }), + child: None, + closed: false + }; + rtdebug!("NEW child latch %x", child.id()); + return child; + } + + pub fn release(~self, local_success: bool) { + // XXX: This should not block, but there's a bug in the below + // code that I can't figure out. + self.wait(local_success); + } + + // XXX: Should not require ~self + fn release_broken(~self, local_success: bool) { + rtassert!(!self.closed); + + rtdebug!("releasing %x", self.id()); + + let id = self.id(); + let _ = id; // XXX: `id` is only used in debug statements so appears unused + let mut this = self; + let mut child_success = true; + let mut children_done = false; + + if this.child.is_some() { + rtdebug!("releasing children"); + let child_link: &mut ChildLink = this.child.get_mut_ref(); + let shared: &mut SharedState = &mut *child_link.shared; + + if !child_link.dropped_child { + let last_count = shared.count.fetch_sub(1, SeqCst); + rtdebug!("child count before sub %u %x", last_count, id); + if last_count == 1 { + assert!(child_link.chan.try_send(ChildrenTerminated)); + } + child_link.dropped_child = true; + } + + // Wait for messages from children + let mut tombstones = ~[]; + loop { + if child_link.port.peek() { + match child_link.port.recv() { + Tombstone(t) => { + tombstones.push(t); + }, + ChildrenTerminated => { + children_done = true; + break; + } + } + } else { + break + } + } + + rtdebug!("releasing %u tombstones %x", tombstones.len(), id); + + // Try to release the tombstones. Those that still have + // outstanding will be re-enqueued. When this task's + // parents release their latch we'll end up back here + // trying them again. + while !tombstones.is_empty() { + tombstones.pop().release(true); + } + + if children_done { + let count = shared.count.load(SeqCst); + assert!(count == 0); + // self_count is the acquire-read barrier + child_success = shared.success; + } + } else { + children_done = true; + } + + let total_success = local_success && child_success; + + rtassert!(this.parent.is_some()); + + unsafe { + { + let parent_link: &mut ParentLink = this.parent.get_mut_ref(); + let shared: *mut SharedState = parent_link.shared; + + if !total_success { + // parent_count is the write-wait barrier + (*shared).success = false; + } + } + + if children_done { + rtdebug!("children done"); + do Local::borrow:: |sched| { + sched.metrics.release_tombstone += 1; + } + { + rtdebug!("RELEASING parent %x", id); + let parent_link: &mut ParentLink = this.parent.get_mut_ref(); + let shared: *mut SharedState = parent_link.shared; + let last_count = (*shared).count.fetch_sub(1, SeqCst); + rtdebug!("count before parent sub %u %x", last_count, id); + if last_count == 1 { + assert!(parent_link.chan.try_send(ChildrenTerminated)); + } + } + this.closed = true; + util::ignore(this); + } else { + rtdebug!("children not done"); + rtdebug!("TOMBSTONING %x", id); + do Local::borrow:: |sched| { + sched.metrics.release_no_tombstone += 1; + } + let chan = { + let parent_link: &mut ParentLink = this.parent.get_mut_ref(); + parent_link.chan.clone() + }; + assert!(chan.try_send(Tombstone(this))); + } + } + } + + // XXX: Should not require ~self + pub fn wait(~self, local_success: bool) -> bool { + rtassert!(!self.closed); + + rtdebug!("WAITING %x", self.id()); + + let mut this = self; + let mut child_success = true; + + if this.child.is_some() { + rtdebug!("waiting for children"); + let child_link: &mut ChildLink = this.child.get_mut_ref(); + let shared: &mut SharedState = &mut *child_link.shared; + + if !child_link.dropped_child { + let last_count = shared.count.fetch_sub(1, SeqCst); + rtdebug!("child count before sub %u", last_count); + if last_count == 1 { + assert!(child_link.chan.try_send(ChildrenTerminated)); + } + child_link.dropped_child = true; + } + + // Wait for messages from children + loop { + match child_link.port.recv() { + Tombstone(t) => { + t.wait(true); + } + ChildrenTerminated => break + } + } + + let count = shared.count.load(SeqCst); + if count != 0 { ::io::println(fmt!("%u", count)); } + assert!(count == 0); + // self_count is the acquire-read barrier + child_success = shared.success; + } + + let total_success = local_success && child_success; + + if this.parent.is_some() { + rtdebug!("releasing parent"); + unsafe { + let parent_link: &mut ParentLink = this.parent.get_mut_ref(); + let shared: *mut SharedState = parent_link.shared; + + if !total_success { + // parent_count is the write-wait barrier + (*shared).success = false; + } + + let last_count = (*shared).count.fetch_sub(1, SeqCst); + rtdebug!("count before parent sub %u", last_count); + if last_count == 1 { + assert!(parent_link.chan.try_send(ChildrenTerminated)); + } + } + } + + this.closed = true; + util::ignore(this); + + return total_success; + } +} + +impl Drop for JoinLatch { + fn finalize(&self) { + rtdebug!("DESTROYING %x", self.id()); + rtassert!(self.closed); + } +} + +#[cfg(test)] +mod test { + use super::*; + use cell::Cell; + use container::Container; + use iter::Times; + use old_iter::BaseIter; + use rt::test::*; + use rand; + use rand::RngUtil; + use vec::{CopyableVector, ImmutableVector}; + + #[test] + fn success_immediately() { + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_immediately { + let child_latch = child_latch.take(); + assert!(child_latch.wait(true)); + } + + assert!(latch.wait(true)); + } + } + + #[test] + fn success_later() { + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_later { + let child_latch = child_latch.take(); + assert!(child_latch.wait(true)); + } + + assert!(latch.wait(true)); + } + } + + #[test] + fn mt_success() { + do run_in_mt_newsched_task { + let mut latch = JoinLatch::new_root(); + + for 10.times { + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_random { + let child_latch = child_latch.take(); + assert!(child_latch.wait(true)); + } + } + + assert!(latch.wait(true)); + } + } + + #[test] + fn mt_failure() { + do run_in_mt_newsched_task { + let mut latch = JoinLatch::new_root(); + + let spawn = |status| { + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_random { + let child_latch = child_latch.take(); + child_latch.wait(status); + } + }; + + for 10.times { spawn(true) } + spawn(false); + for 10.times { spawn(true) } + + assert!(!latch.wait(true)); + } + } + + #[test] + fn mt_multi_level_success() { + do run_in_mt_newsched_task { + let mut latch = JoinLatch::new_root(); + + fn child(latch: &mut JoinLatch, i: int) { + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_random { + let mut child_latch = child_latch.take(); + if i != 0 { + child(&mut *child_latch, i - 1); + child_latch.wait(true); + } else { + child_latch.wait(true); + } + } + } + + child(&mut *latch, 10); + + assert!(latch.wait(true)); + } + } + + #[test] + fn mt_multi_level_failure() { + do run_in_mt_newsched_task { + let mut latch = JoinLatch::new_root(); + + fn child(latch: &mut JoinLatch, i: int) { + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_random { + let mut child_latch = child_latch.take(); + if i != 0 { + child(&mut *child_latch, i - 1); + child_latch.wait(false); + } else { + child_latch.wait(true); + } + } + } + + child(&mut *latch, 10); + + assert!(!latch.wait(true)); + } + } + + #[test] + fn release_child() { + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + + do spawntask_immediately { + let latch = child_latch.take(); + latch.release(false); + } + + assert!(!latch.wait(true)); + } + } + + #[test] + fn release_child_tombstone() { + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + + do spawntask_immediately { + let mut latch = child_latch.take(); + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_later { + let latch = child_latch.take(); + latch.release(false); + } + latch.release(true); + } + + assert!(!latch.wait(true)); + } + } + + #[test] + fn release_child_no_tombstone() { + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + + do spawntask_later { + let mut latch = child_latch.take(); + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + do spawntask_immediately { + let latch = child_latch.take(); + latch.release(false); + } + latch.release(true); + } + + assert!(!latch.wait(true)); + } + } + + #[test] + fn release_child_tombstone_stress() { + fn rand_orders() -> ~[bool] { + let mut v = ~[false,.. 5]; + v[0] = true; + let mut rng = rand::rng(); + return rng.shuffle(v); + } + + fn split_orders(orders: &[bool]) -> (~[bool], ~[bool]) { + if orders.is_empty() { + return (~[], ~[]); + } else if orders.len() <= 2 { + return (orders.to_owned(), ~[]); + } + let mut rng = rand::rng(); + let n = rng.gen_uint_range(1, orders.len()); + let first = orders.slice(0, n).to_owned(); + let last = orders.slice(n, orders.len()).to_owned(); + assert!(first.len() + last.len() == orders.len()); + return (first, last); + } + + for stress_factor().times { + do run_in_newsched_task { + fn doit(latch: &mut JoinLatch, orders: ~[bool], depth: uint) { + let (my_orders, remaining_orders) = split_orders(orders); + rtdebug!("(my_orders, remaining): %?", (&my_orders, &remaining_orders)); + rtdebug!("depth: %u", depth); + let mut remaining_orders = remaining_orders; + let mut num = 0; + for my_orders.each |&order| { + let child_latch = latch.new_child(); + let child_latch = Cell(child_latch); + let (child_orders, remaining) = split_orders(remaining_orders); + rtdebug!("(child_orders, remaining): %?", (&child_orders, &remaining)); + remaining_orders = remaining; + let child_orders = Cell(child_orders); + let child_num = num; + let _ = child_num; // XXX unused except in rtdebug! + do spawntask_random { + rtdebug!("depth %u num %u", depth, child_num); + let mut child_latch = child_latch.take(); + let child_orders = child_orders.take(); + doit(&mut *child_latch, child_orders, depth + 1); + child_latch.release(order); + } + + num += 1; + } + } + + let mut latch = JoinLatch::new_root(); + let orders = rand_orders(); + rtdebug!("orders: %?", orders); + + doit(&mut *latch, orders, 0); + + assert!(!latch.wait(true)); + } + } + } + + #[test] + fn whateverman() { + struct Order { + immediate: bool, + succeed: bool, + orders: ~[Order] + } + fn next(latch: &mut JoinLatch, orders: ~[Order]) { + for orders.each |order| { + let suborders = copy order.orders; + let child_latch = Cell(latch.new_child()); + let succeed = order.succeed; + if order.immediate { + do spawntask_immediately { + let mut child_latch = child_latch.take(); + next(&mut *child_latch, copy suborders); + rtdebug!("immediate releasing"); + child_latch.release(succeed); + } + } else { + do spawntask_later { + let mut child_latch = child_latch.take(); + next(&mut *child_latch, copy suborders); + rtdebug!("later releasing"); + child_latch.release(succeed); + } + } + } + } + + do run_in_newsched_task { + let mut latch = JoinLatch::new_root(); + let orders = ~[ Order { // 0 0 + immediate: true, + succeed: true, + orders: ~[ Order { // 1 0 + immediate: true, + succeed: false, + orders: ~[ Order { // 2 0 + immediate: false, + succeed: false, + orders: ~[ Order { // 3 0 + immediate: true, + succeed: false, + orders: ~[] + }, Order { // 3 1 + immediate: false, + succeed: false, + orders: ~[] + }] + }] + }] + }]; + + next(&mut *latch, orders); + assert!(!latch.wait(true)); + } + } +} diff --git a/src/libstd/rt/metrics.rs b/src/libstd/rt/metrics.rs index 70e347fdfb6a..b0c0fa5d7086 100644 --- a/src/libstd/rt/metrics.rs +++ b/src/libstd/rt/metrics.rs @@ -34,7 +34,11 @@ pub struct SchedMetrics { // Message receives that do not block the receiver rendezvous_recvs: uint, // Message receives that block the receiver - non_rendezvous_recvs: uint + non_rendezvous_recvs: uint, + // JoinLatch releases that create tombstones + release_tombstone: uint, + // JoinLatch releases that do not create tombstones + release_no_tombstone: uint, } impl SchedMetrics { @@ -51,7 +55,9 @@ impl SchedMetrics { rendezvous_sends: 0, non_rendezvous_sends: 0, rendezvous_recvs: 0, - non_rendezvous_recvs: 0 + non_rendezvous_recvs: 0, + release_tombstone: 0, + release_no_tombstone: 0 } } } @@ -70,6 +76,8 @@ impl ToStr for SchedMetrics { non_rendezvous_sends: %u\n\ rendezvous_recvs: %u\n\ non_rendezvous_recvs: %u\n\ + release_tombstone: %u\n\ + release_no_tombstone: %u\n\ ", self.turns, self.messages_received, @@ -82,7 +90,9 @@ impl ToStr for SchedMetrics { self.rendezvous_sends, self.non_rendezvous_sends, self.rendezvous_recvs, - self.non_rendezvous_recvs + self.non_rendezvous_recvs, + self.release_tombstone, + self.release_no_tombstone ) } } \ No newline at end of file diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index caf3e15e535a..2008c4a180f6 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -133,6 +133,9 @@ pub mod local_ptr; /// Bindings to pthread/windows thread-local storage. pub mod thread_local_storage; +/// A concurrent data structure with which parent tasks wait on child tasks. +pub mod join_latch; + pub mod metrics; diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 97a1c26ed4dc..104eb4b8baef 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -151,7 +151,10 @@ pub impl Scheduler { // XXX: Reenable this once we're using a per-task queue. With a shared // queue this is not true //assert!(sched.work_queue.is_empty()); - rtdebug!("scheduler metrics: %s\n", sched.metrics.to_str()); + rtdebug!("scheduler metrics: %s\n", { + use to_str::ToStr; + sched.metrics.to_str() + }); return sched; } From fd148cd3e2d08ce15272f0690f6e41d2e85ee721 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 13 Jun 2013 22:43:20 -0700 Subject: [PATCH 39/55] std::rt: Change the Task constructors to reflect a tree --- src/libstd/rt/mod.rs | 4 ++-- src/libstd/rt/sched.rs | 22 +++++++++---------- src/libstd/rt/task.rs | 26 +++++++++++++++++++++-- src/libstd/rt/test.rs | 46 +++++++++++++++++++++++++++++++--------- src/libstd/task/spawn.rs | 9 +++++++- 5 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 2008c4a180f6..a65b07fdbcf2 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -167,7 +167,7 @@ pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { let sleepers = SleeperList::new(); let mut sched = ~Scheduler::new(loop_, work_queue, sleepers); sched.no_sleep = true; - let main_task = ~Coroutine::new(&mut sched.stack_pool, main); + let main_task = ~Coroutine::new_root(&mut sched.stack_pool, main); sched.enqueue_task(main_task); sched.run(); @@ -241,7 +241,7 @@ fn test_context() { do run_in_bare_thread { assert_eq!(context(), GlobalContext); let mut sched = ~new_test_uv_sched(); - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let task = ~do Coroutine::new_root(&mut sched.stack_pool) { assert_eq!(context(), TaskContext); let sched = Local::take::(); do sched.deschedule_running_task_and_then() |sched, task| { diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 104eb4b8baef..9abcc2ec3cc9 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -518,8 +518,8 @@ impl SchedHandle { } pub impl Coroutine { - fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { - Coroutine::with_task(stack_pool, ~Task::new(), start) + fn new_root(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { + Coroutine::with_task(stack_pool, ~Task::new_root(), start) } fn with_task(stack_pool: &mut StackPool, @@ -614,7 +614,7 @@ mod test { let task_ran_ptr: *mut bool = &mut task_ran; let mut sched = ~new_test_uv_sched(); - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let task = ~do Coroutine::new_root(&mut sched.stack_pool) { unsafe { *task_ran_ptr = true; } }; sched.enqueue_task(task); @@ -632,7 +632,7 @@ mod test { let mut sched = ~new_test_uv_sched(); for int::range(0, total) |_| { - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let task = ~do Coroutine::new_root(&mut sched.stack_pool) { unsafe { *task_count_ptr = *task_count_ptr + 1; } }; sched.enqueue_task(task); @@ -649,10 +649,10 @@ mod test { let count_ptr: *mut int = &mut count; let mut sched = ~new_test_uv_sched(); - let task1 = ~do Coroutine::new(&mut sched.stack_pool) { + let task1 = ~do Coroutine::new_root(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; } let mut sched = Local::take::(); - let task2 = ~do Coroutine::new(&mut sched.stack_pool) { + let task2 = ~do Coroutine::new_root(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; } }; // Context switch directly to the new task @@ -677,7 +677,7 @@ mod test { let mut sched = ~new_test_uv_sched(); - let start_task = ~do Coroutine::new(&mut sched.stack_pool) { + let start_task = ~do Coroutine::new_root(&mut sched.stack_pool) { run_task(count_ptr); }; sched.enqueue_task(start_task); @@ -687,7 +687,7 @@ mod test { fn run_task(count_ptr: *mut int) { do Local::borrow:: |sched| { - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let task = ~do Coroutine::new_root(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; if *count_ptr != MAX { @@ -705,7 +705,7 @@ mod test { fn test_block_task() { do run_in_bare_thread { let mut sched = ~new_test_uv_sched(); - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let task = ~do Coroutine::new_root(&mut sched.stack_pool) { let sched = Local::take::(); assert!(sched.in_task_context()); do sched.deschedule_running_task_and_then() |sched, task| { @@ -752,13 +752,13 @@ mod test { let mut sched1 = ~new_test_uv_sched(); let handle1 = sched1.make_handle(); let handle1_cell = Cell(handle1); - let task1 = ~do Coroutine::new(&mut sched1.stack_pool) { + let task1 = ~do Coroutine::new_root(&mut sched1.stack_pool) { chan_cell.take().send(()); }; sched1.enqueue_task(task1); let mut sched2 = ~new_test_uv_sched(); - let task2 = ~do Coroutine::new(&mut sched2.stack_pool) { + let task2 = ~do Coroutine::new_root(&mut sched2.stack_pool) { port_cell.take().recv(); // Release the other scheduler's handle so it can exit handle1_cell.take(); diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index cf4967b12b30..10b4672df05b 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -37,7 +37,7 @@ pub struct Unwinder { } impl Task { - pub fn new() -> Task { + pub fn new_root() -> Task { Task { heap: LocalHeap::new(), gc: GarbageCollector, @@ -48,7 +48,29 @@ impl Task { } } - pub fn without_unwinding() -> Task { + pub fn new_root_without_unwinding() -> Task { + Task { + heap: LocalHeap::new(), + gc: GarbageCollector, + storage: LocalStorage(ptr::null(), None), + logger: StdErrLogger, + unwinder: None, + destroyed: false + } + } + + pub fn new_child(&mut self) -> Task { + Task { + heap: LocalHeap::new(), + gc: GarbageCollector, + storage: LocalStorage(ptr::null(), None), + logger: StdErrLogger, + unwinder: Some(Unwinder { unwinding: false }), + destroyed: false + } + } + + pub fn new_child_without_unwinding(&mut self) -> Task { Task { heap: LocalHeap::new(), gc: GarbageCollector, diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index c8df3a612033..4a4d498a26e7 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -48,7 +48,7 @@ pub fn run_in_newsched_task(f: ~fn()) { do run_in_bare_thread { let mut sched = ~new_test_uv_sched(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + ~Task::new_root_without_unwinding(), f.take()); sched.enqueue_task(task); sched.run(); @@ -94,7 +94,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let f_cell = Cell(f_cell.take()); let handles = Cell(handles); - let main_task = ~do Coroutine::new(&mut scheds[0].stack_pool) { + let main_task = ~do Coroutine::new_root(&mut scheds[0].stack_pool) { f_cell.take()(); let mut handles = handles.take(); @@ -132,9 +132,14 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { pub fn spawntask(f: ~fn()) { use super::sched::*; + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } + let mut sched = Local::take::(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + task.swap_unwrap(), f); sched.schedule_new_task(task); } @@ -143,9 +148,14 @@ pub fn spawntask(f: ~fn()) { pub fn spawntask_immediately(f: ~fn()) { use super::sched::*; + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } + let mut sched = Local::take::(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + task.swap_unwrap(), f); do sched.switch_running_tasks_and_then(task) |sched, task| { sched.enqueue_task(task); @@ -156,9 +166,14 @@ pub fn spawntask_immediately(f: ~fn()) { pub fn spawntask_later(f: ~fn()) { use super::sched::*; + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } + let mut sched = Local::take::(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + task.swap_unwrap(), f); sched.enqueue_task(task); @@ -170,14 +185,19 @@ pub fn spawntask_random(f: ~fn()) { use super::sched::*; use rand::{Rand, rng}; - let mut rng = rng(); - let run_now: bool = Rand::rand(&mut rng); + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } let mut sched = Local::take::(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + task.swap_unwrap(), f); + let mut rng = rng(); + let run_now: bool = Rand::rand(&mut rng); + if run_now { do sched.switch_running_tasks_and_then(task) |sched, task| { sched.enqueue_task(task); @@ -206,7 +226,7 @@ pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { do sched.deschedule_running_task_and_then() |sched, old_task| { let old_task = Cell(old_task); let f = f.take(); - let new_task = ~do Coroutine::new(&mut sched.stack_pool) { + let new_task = ~do Coroutine::new_root(&mut sched.stack_pool) { do (|| { (f.take())() }).finally { @@ -229,11 +249,17 @@ pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { pub fn spawntask_thread(f: ~fn()) -> Thread { use rt::sched::*; + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } + + let task = Cell(task.swap_unwrap()); let f = Cell(f); let thread = do Thread::start { let mut sched = ~new_test_uv_sched(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), + task.take(), f.take()); sched.enqueue_task(task); sched.run(); diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs index 5941221821a8..a4fbec11d723 100644 --- a/src/libstd/task/spawn.rs +++ b/src/libstd/task/spawn.rs @@ -91,6 +91,7 @@ use uint; use util; use unstable::sync::{Exclusive, exclusive}; use rt::local::Local; +use rt::task::Task; #[cfg(test)] use task::default_task_opts; @@ -576,8 +577,14 @@ pub fn spawn_raw(opts: TaskOpts, f: ~fn()) { fn spawn_raw_newsched(_opts: TaskOpts, f: ~fn()) { use rt::sched::*; + let mut task = None; + do Local::borrow::() |running_task| { + task = Some(~running_task.new_child_without_unwinding()); + } + let mut sched = Local::take::(); - let task = ~Coroutine::new(&mut sched.stack_pool, f); + let task = ~Coroutine::with_task(&mut sched.stack_pool, + task.swap_unwrap(), f); sched.schedule_new_task(task); } From 90fbe38f0064836fd5e169c520d3fd19953e5604 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 13 Jun 2013 23:16:27 -0700 Subject: [PATCH 40/55] std::rt: Tasks must have an unwinder. Simpler --- src/libstd/rt/task.rs | 39 ++++----------------------------------- src/libstd/rt/test.rs | 12 ++++++------ src/libstd/sys.rs | 6 +----- src/libstd/task/mod.rs | 11 +---------- src/libstd/task/spawn.rs | 2 +- 5 files changed, 13 insertions(+), 57 deletions(-) diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index 10b4672df05b..7c08dabf0bd8 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -25,7 +25,7 @@ pub struct Task { gc: GarbageCollector, storage: LocalStorage, logger: StdErrLogger, - unwinder: Option, + unwinder: Unwinder, destroyed: bool } @@ -43,18 +43,7 @@ impl Task { gc: GarbageCollector, storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, - unwinder: Some(Unwinder { unwinding: false }), - destroyed: false - } - } - - pub fn new_root_without_unwinding() -> Task { - Task { - heap: LocalHeap::new(), - gc: GarbageCollector, - storage: LocalStorage(ptr::null(), None), - logger: StdErrLogger, - unwinder: None, + unwinder: Unwinder { unwinding: false }, destroyed: false } } @@ -65,18 +54,7 @@ impl Task { gc: GarbageCollector, storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, - unwinder: Some(Unwinder { unwinding: false }), - destroyed: false - } - } - - pub fn new_child_without_unwinding(&mut self) -> Task { - Task { - heap: LocalHeap::new(), - gc: GarbageCollector, - storage: LocalStorage(ptr::null(), None), - logger: StdErrLogger, - unwinder: None, + unwinder: Unwinder { unwinding: false }, destroyed: false } } @@ -88,16 +66,7 @@ impl Task { assert!(ptr::ref_eq(task, self)); } - match self.unwinder { - Some(ref mut unwinder) => { - // If there's an unwinder then set up the catch block - unwinder.try(f); - } - None => { - // Otherwise, just run the body - f() - } - } + self.unwinder.try(f); self.destroy(); } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index 4a4d498a26e7..ecfe93560b4b 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -48,7 +48,7 @@ pub fn run_in_newsched_task(f: ~fn()) { do run_in_bare_thread { let mut sched = ~new_test_uv_sched(); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::new_root_without_unwinding(), + ~Task::new_root(), f.take()); sched.enqueue_task(task); sched.run(); @@ -134,7 +134,7 @@ pub fn spawntask(f: ~fn()) { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let mut sched = Local::take::(); @@ -150,7 +150,7 @@ pub fn spawntask_immediately(f: ~fn()) { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let mut sched = Local::take::(); @@ -168,7 +168,7 @@ pub fn spawntask_later(f: ~fn()) { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let mut sched = Local::take::(); @@ -187,7 +187,7 @@ pub fn spawntask_random(f: ~fn()) { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let mut sched = Local::take::(); @@ -251,7 +251,7 @@ pub fn spawntask_thread(f: ~fn()) -> Thread { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let task = Cell(task.swap_unwrap()); diff --git a/src/libstd/sys.rs b/src/libstd/sys.rs index 137070ce2021..77085d195678 100644 --- a/src/libstd/sys.rs +++ b/src/libstd/sys.rs @@ -226,11 +226,7 @@ pub fn begin_unwind_(msg: *c_char, file: *c_char, line: size_t) -> ! { gc::cleanup_stack_for_failure(); let task = Local::unsafe_borrow::(); - let unwinder: &mut Option = &mut (*task).unwinder; - match *unwinder { - Some(ref mut unwinder) => unwinder.begin_unwind(), - None => abort!("failure without unwinder. aborting process") - } + (*task).unwinder.begin_unwind(); } } } diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs index f24d2327358b..faa505c19951 100644 --- a/src/libstd/task/mod.rs +++ b/src/libstd/task/mod.rs @@ -515,16 +515,7 @@ pub fn failing() -> bool { _ => { let mut unwinding = false; do Local::borrow:: |local| { - unwinding = match local.unwinder { - Some(unwinder) => { - unwinder.unwinding - } - None => { - // Because there is no unwinder we can't be unwinding. - // (The process will abort on failure) - false - } - } + unwinding = local.unwinder.unwinding } return unwinding; } diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs index a4fbec11d723..a17a6777a98f 100644 --- a/src/libstd/task/spawn.rs +++ b/src/libstd/task/spawn.rs @@ -579,7 +579,7 @@ fn spawn_raw_newsched(_opts: TaskOpts, f: ~fn()) { let mut task = None; do Local::borrow::() |running_task| { - task = Some(~running_task.new_child_without_unwinding()); + task = Some(~running_task.new_child()); } let mut sched = Local::take::(); From d1ec8b5fb85cb6fd4caed64223c5cb3fd920daab Mon Sep 17 00:00:00 2001 From: toddaaro Date: Fri, 14 Jun 2013 12:17:56 -0700 Subject: [PATCH 41/55] redesigned the pinning to pin deal with things on dequeue, not on enqueue --- src/libstd/macros.rs | 11 - src/libstd/rt/local.rs | 6 +- src/libstd/rt/sched.rs | 535 ++++++++++++++++++++++++++------------- src/libstd/rt/task.rs | 14 +- src/libstd/rt/test.rs | 32 ++- src/libstd/task/spawn.rs | 2 +- 6 files changed, 390 insertions(+), 210 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index bf5b36c7580a..b01bd8f993c0 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -49,18 +49,7 @@ pub fn do_abort() -> ! { macro_rules! abort( ($( $msg:expr),+) => ( { rtdebug!($($msg),+); - -// do_abort(); - - // NB: This is in a fn to avoid putting the `unsafe` block in - // a macro, which causes spurious 'unnecessary unsafe block' - // warnings. -// fn do_abort() -> ! { -// unsafe { ::libc::abort(); } -// } - ::macros::do_abort(); - } ) ) diff --git a/src/libstd/rt/local.rs b/src/libstd/rt/local.rs index 359cf5fc3e17..6e0fbda5ec9a 100644 --- a/src/libstd/rt/local.rs +++ b/src/libstd/rt/local.rs @@ -30,7 +30,7 @@ impl Local for Scheduler { fn borrow(f: &fn(&mut Scheduler) -> T) -> T { let mut res: Option = None; let res_ptr: *mut Option = &mut res; - unsafe { + unsafe { do local_ptr::borrow |sched| { let result = f(sched); *res_ptr = Some(result); @@ -39,7 +39,7 @@ impl Local for Scheduler { match res { Some(r) => { r } None => abort!("function failed!") - } + } } unsafe fn unsafe_borrow() -> *mut Scheduler { local_ptr::unsafe_borrow() } unsafe fn try_unsafe_borrow() -> Option<*mut Scheduler> { abort!("unimpl") } @@ -139,5 +139,5 @@ mod test { assert!(res) let _scheduler: ~Scheduler = Local::take(); } - + } diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 4bc61d638248..3b8a31d1840b 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -90,27 +90,10 @@ pub struct Coroutine { priv saved_context: Context, /// The heap, GC, unwinding, local storage, logging task: ~Task, - /// The scheduler that this task calls home - home_sched: SchedHome } -// To send a Coroutine to another task we have to use contained home -// information (the SchedHandle). So we need a form that doesn't -// include one. - -// XXX perf: Evaluate this structure - there should be a clever way to -// make it such that we don't need to deal with building/destructing -// on Coroutines that aren't homed. - -pub struct HomelessCoroutine { - priv current_stack_segment: StackSegment, - priv saved_context: Context, - task: ~Task -} - // A scheduler home is either a handle to the home scheduler, or an // explicit "AnySched". - pub enum SchedHome { AnySched, Sched(SchedHandle) @@ -119,7 +102,7 @@ pub enum SchedHome { pub enum SchedMessage { Wake, Shutdown, - BiasedTask(~HomelessCoroutine) + PinnedTask(~Coroutine) } enum CleanupJob { @@ -193,6 +176,7 @@ pub impl Scheduler { (*event_loop).run(); } + rtdebug!("run taking sched"); let sched = Local::take::(); // XXX: Reenable this once we're using a per-task queue. With a shared // queue this is not true @@ -214,6 +198,7 @@ pub impl Scheduler { if sched.interpret_message_queue() { // We performed a scheduling action. There may be other work // to do yet, so let's try again later. + rtdebug!("run_sched_once, interpret_message_queue taking sched"); let mut sched = Local::take::(); sched.metrics.messages_received += 1; sched.event_loop.callback(Scheduler::run_sched_once); @@ -222,6 +207,7 @@ pub impl Scheduler { } // Now, look in the work queue for tasks to run + rtdebug!("run_sched_once taking"); let sched = Local::take::(); if sched.resume_task_from_queue() { // We performed a scheduling action. There may be other work @@ -271,7 +257,7 @@ pub impl Scheduler { // We don't want to queue tasks that belong on other threads, // so we send them home at enqueue time. - + // The borrow checker doesn't like our disassembly of the // Coroutine struct and partial use and mutation of the // fields. So completely disassemble here and stop using? @@ -283,95 +269,31 @@ pub impl Scheduler { let this = self; - match task { - ~Coroutine { current_stack_segment: css, - saved_context: sc, - task: t, - home_sched: home_sched } => { - - let mut home_sched = home_sched; + // We push the task onto our local queue clone. + this.work_queue.push(task); + this.event_loop.callback(Scheduler::run_sched_once); - match home_sched { - Sched(ref mut home_handle) - if home_handle.sched_id != this.sched_id() => { + // We've made work available. Notify a + // sleeping scheduler. - // In this branch we know the task is not - // home, so we send it home. + // XXX: perf. Check for a sleeper without + // synchronizing memory. It's not critical + // that we always find it. - rtdebug!("home_handle_id: %u, loc: %u", - home_handle.sched_id, - this.sched_id()); - let homeless = ~HomelessCoroutine { - current_stack_segment: css, - saved_context: sc, - task: t - }; - home_handle.send(BiasedTask(homeless)); - rtdebug!("sent task home"); - return (); - } - Sched( ref mut home_handle) => { - - // Here we know the task is home, so we need - // to "keep" it home. Since we don't have a - // scheduler-local queue for this purpose, we - // just use our message queue. - - rtdebug!("homed task at home, sending to self"); - let homeless = ~HomelessCoroutine { - current_stack_segment: css, - saved_context: sc, - task: t - }; - home_handle.send(BiasedTask(homeless)); - rtdebug!("sent home to self"); - return (); - - } - _ => { - - // We just destroyed our Coroutine ... but now - // we want it back. Build a new one? - // XXX: perf: see above comment about not - // destroying - - let task = ~Coroutine { - current_stack_segment: css, - saved_context: sc, - task: t, - home_sched: AnySched }; - - - // We push the task onto our local queue. - this.work_queue.push(task); - this.event_loop.callback(Scheduler::run_sched_once); - - // We've made work available. Notify a - // sleeping scheduler. - - // XXX: perf. Check for a sleeper without - // synchronizing memory. It's not critical - // that we always find it. - - // XXX: perf. If there's a sleeper then we - // might as well just send it the task - // directly instead of pushing it to the - // queue. That is essentially the intent here - // and it is less work. - match this.sleeper_list.pop() { - Some(handle) => { - let mut handle = handle; - handle.send(Wake) - } - None => { (/* pass */) } - }; - } - } + // XXX: perf. If there's a sleeper then we + // might as well just send it the task + // directly instead of pushing it to the + // queue. That is essentially the intent here + // and it is less work. + match this.sleeper_list.pop() { + Some(handle) => { + let mut handle = handle; + handle.send(Wake) } - } + None => { (/* pass */) } + }; } - // * Scheduler-context operations fn interpret_message_queue(~self) -> bool { @@ -381,23 +303,11 @@ pub impl Scheduler { let mut this = self; match this.message_queue.pop() { - Some(BiasedTask(~HomelessCoroutine { - current_stack_segment: css, - saved_context: sc, - task: t})) => { + Some(PinnedTask(task)) => { rtdebug!("recv BiasedTask message in sched: %u", this.sched_id()); - - // Since this was the "send home" message for a task, - // we know that this is the home. So we rebuild the - // sched_handle. - - let task = ~Coroutine { - current_stack_segment: css, - saved_context: sc, - task: t, - home_sched: Sched(this.make_handle()) - }; + let mut task = task; + task.task.home = Some(Sched(this.make_handle())); this.resume_task_immediately(task); return true; } @@ -438,32 +348,93 @@ pub impl Scheduler { } } + /// Given an input Coroutine sends it back to its home scheduler. + fn send_task_home(task: ~Coroutine) { + let mut task = task; + let mut home = task.task.home.swap_unwrap(); + match home { + Sched(ref mut home_handle) => { + home_handle.send(PinnedTask(task)); + } + AnySched => { + abort!("error: cannot send anysched task home"); + } + } + } + + // Resume a task from the queue - but also take into account that + // it might not belong here. fn resume_task_from_queue(~self) -> bool { assert!(!self.in_task_context()); rtdebug!("looking in work queue for task to schedule"); let mut this = self; - if this.run_anything { - match this.work_queue.pop() { - Some(task) => { - rtdebug!("resuming task from work queue"); - this.resume_task_immediately(task); - return true; - } - None => { - rtdebug!("no tasks in queue"); - Local::put(this); - return false; + // The borrow checker imposes the possibly absurd requirement + // that we split this into two match expressions. This is due + // to the inspection of the internal bits of task, as that + // can't be in scope when we act on task. + match this.work_queue.pop() { + Some(task) => { + let action_id = { + let home = &task.task.home; + match home { + &Some(Sched(ref home_handle)) + if home_handle.sched_id != this.sched_id() => { + 0 + } + &Some(AnySched) if this.run_anything => { + 1 + } + &Some(AnySched) => { + 2 + } + &Some(Sched(_)) => { + 3 + } + &None => { + 4 + } + } + }; + + match action_id { + 0 => { + rtdebug!("sending task home"); + Scheduler::send_task_home(task); + Local::put(this); + return false; + } + 1 => { + rtdebug!("resuming now"); + this.resume_task_immediately(task); + return true; + } + 2 => { + rtdebug!("re-queueing") + this.enqueue_task(task); + Local::put(this); + return false; + } + 3 => { + rtdebug!("resuming now"); + this.resume_task_immediately(task); + return true; + } + 4 => { + abort!("task home was None!"); + } + _ => { + abort!("literally, you should not be here"); + } } } - } else { - // In this branch we have a scheduler that is not allowed - // to run unpinned tasks. As such it will only get tasks - // to run from the message queue. - rtdebug!("skipping resume_task_from_queue"); - Local::put(this); - return false; + + None => { + rtdebug!("no tasks in queue"); + Local::put(this); + return false; + } } } @@ -484,21 +455,32 @@ pub impl Scheduler { abort!("control reached end of task"); } - fn schedule_new_task(~self, task: ~Coroutine) { + pub fn schedule_task(~self, task: ~Coroutine) { assert!(self.in_task_context()); - do self.switch_running_tasks_and_then(task) |sched, last_task| { - let last_task = Cell(last_task); - sched.enqueue_task(last_task.take()); - } - } + // is the task home? + let is_home = task.is_home_no_tls(&self); - fn schedule_task(~self, task: ~Coroutine) { - assert!(self.in_task_context()); + // does the task have a home? + let homed = task.homed(); - do self.switch_running_tasks_and_then(task) |sched, last_task| { - let last_task = Cell(last_task); - sched.enqueue_task(last_task.take()); + let mut this = self; + + if is_home || (!homed && this.run_anything) { + // here we know we are home, execute now OR we know we + // aren't homed, and that this sched doesn't care + do this.switch_running_tasks_and_then(task) |sched, last_task| { + let last_task = Cell(last_task); + sched.enqueue_task(last_task.take()); + } + } else if !homed && !this.run_anything { + // the task isn't homed, but it can't be run here + this.enqueue_task(task); + Local::put(this); + } else { + // task isn't home, so don't run it here, send it home + Scheduler::send_task_home(task); + Local::put(this); } } @@ -681,19 +663,66 @@ impl SchedHandle { pub impl Coroutine { - - /// This function checks that a coroutine is running "home". - fn am_home(&self) -> bool { + /// This function checks that a coroutine is running "home". + fn is_home(&self) -> bool { + rtdebug!("checking if coroutine is home"); do Local::borrow:: |sched| { - match self.home_sched { - AnySched => { true } - Sched(SchedHandle { sched_id: ref id, _ }) => { + match self.task.home { + Some(AnySched) => { false } + Some(Sched(SchedHandle { sched_id: ref id, _ })) => { *id == sched.sched_id() } + None => { abort!("error: homeless task!"); } } } - } - + } + + /// Without access to self, but with access to the "expected home + /// id", see if we are home. + fn is_home_using_id(id: uint) -> bool { + rtdebug!("checking if coroutine is home using id"); + do Local::borrow:: |sched| { + if sched.sched_id() == id { + true + } else { + false + } + } + } + + /// Check if this coroutine has a home + fn homed(&self) -> bool { + rtdebug!("checking if this coroutine has a home"); + match self.task.home { + Some(AnySched) => { false } + Some(Sched(_)) => { true } + None => { abort!("error: homeless task!"); + } + } + } + + /// A version of is_home that does not need to use TLS, it instead + /// takes local scheduler as a parameter. + fn is_home_no_tls(&self, sched: &~Scheduler) -> bool { + rtdebug!("checking if coroutine is home without tls"); + match self.task.home { + Some(AnySched) => { true } + Some(Sched(SchedHandle { sched_id: ref id, _})) => { + *id == sched.sched_id() + } + None => { abort!("error: homeless task!"); } + } + } + + /// Check TLS for the scheduler to see if we are on a special + /// scheduler. + pub fn on_special() -> bool { + rtdebug!("checking if coroutine is executing on special sched"); + do Local::borrow::() |sched| { + !sched.run_anything + } + } + // Created new variants of "new" that takes a home scheduler // parameter. The original with_task now calls with_task_homed // using the AnySched paramter. @@ -710,19 +739,20 @@ pub impl Coroutine { task: ~Task, start: ~fn(), home: SchedHome) -> Coroutine { - + static MIN_STACK_SIZE: uint = 10000000; // XXX: Too much stack - + let start = Coroutine::build_start_wrapper(start); let mut stack = stack_pool.take_segment(MIN_STACK_SIZE); // NB: Context holds a pointer to that ~fn let initial_context = Context::new(start, &mut stack); - return Coroutine { + let mut crt = Coroutine { current_stack_segment: stack, saved_context: initial_context, task: task, - home_sched: home }; + crt.task.home = Some(home); + return crt; } fn with_task(stack_pool: &mut StackPool, @@ -841,7 +871,7 @@ mod test { let sched_handle = sched.make_handle(); let sched_id = sched.sched_id(); - + let task = ~do Coroutine::new_homed(&mut sched.stack_pool, Sched(sched_handle)) { unsafe { *task_ran_ptr = true }; @@ -855,6 +885,146 @@ mod test { } } + // A test for each state of schedule_task + + #[test] + fn test_schedule_home_states() { + + use rt::uv::uvio::UvEventLoop; + use rt::sched::Shutdown; + use rt::sleeper_list::SleeperList; + use rt::work_queue::WorkQueue; + + do run_in_bare_thread { +// let nthreads = 2; + + let sleepers = SleeperList::new(); + let work_queue = WorkQueue::new(); + + // our normal scheduler + let mut normal_sched = ~Scheduler::new( + ~UvEventLoop::new(), + work_queue.clone(), + sleepers.clone()); + + let normal_handle = Cell(normal_sched.make_handle()); + + // our special scheduler + let mut special_sched = ~Scheduler::new_special( + ~UvEventLoop::new(), + work_queue.clone(), + sleepers.clone(), + true); + + let special_handle = Cell(special_sched.make_handle()); + let special_handle2 = Cell(special_sched.make_handle()); + let special_id = special_sched.sched_id(); + let t1_handle = special_sched.make_handle(); + let t4_handle = special_sched.make_handle(); + + let t1f = ~do Coroutine::new_homed(&mut special_sched.stack_pool, + Sched(t1_handle)) { + let is_home = Coroutine::is_home_using_id(special_id); + rtdebug!("t1 should be home: %b", is_home); + assert!(is_home); + }; + let t1f = Cell(t1f); + + let t2f = ~do Coroutine::new(&mut normal_sched.stack_pool) { + let on_special = Coroutine::on_special(); + rtdebug!("t2 should not be on special: %b", on_special); + assert!(!on_special); + }; + let t2f = Cell(t2f); + + let t3f = ~do Coroutine::new(&mut normal_sched.stack_pool) { + // not on special + let on_special = Coroutine::on_special(); + rtdebug!("t3 should not be on special: %b", on_special); + assert!(!on_special); + }; + let t3f = Cell(t3f); + + let t4f = ~do Coroutine::new_homed(&mut special_sched.stack_pool, + Sched(t4_handle)) { + // is home + let home = Coroutine::is_home_using_id(special_id); + rtdebug!("t4 should be home: %b", home); + assert!(home); + }; + let t4f = Cell(t4f); + + // we have four tests, make them as closures + let t1: ~fn() = || { + // task is home on special + let task = t1f.take(); + let sched = Local::take::(); + sched.schedule_task(task); + }; + let t2: ~fn() = || { + // not homed, task doesn't care + let task = t2f.take(); + let sched = Local::take::(); + sched.schedule_task(task); + }; + let t3: ~fn() = || { + // task not homed, must leave + let task = t3f.take(); + let sched = Local::take::(); + sched.schedule_task(task); + }; + let t4: ~fn() = || { + // task not home, send home + let task = t4f.take(); + let sched = Local::take::(); + sched.schedule_task(task); + }; + + let t1 = Cell(t1); + let t2 = Cell(t2); + let t3 = Cell(t3); + let t4 = Cell(t4); + + // build a main task that runs our four tests + let main_task = ~do Coroutine::new(&mut normal_sched.stack_pool) { + // the two tasks that require a normal start location + t2.take()(); + t4.take()(); + normal_handle.take().send(Shutdown); + special_handle.take().send(Shutdown); + }; + + // task to run the two "special start" tests + let special_task = ~do Coroutine::new_homed( + &mut special_sched.stack_pool, + Sched(special_handle2.take())) { + t1.take()(); + t3.take()(); + }; + + // enqueue the main tasks + normal_sched.enqueue_task(special_task); + normal_sched.enqueue_task(main_task); + + let nsched_cell = Cell(normal_sched); + let normal_thread = do Thread::start { + let sched = nsched_cell.take(); + sched.run(); + }; + + let ssched_cell = Cell(special_sched); + let special_thread = do Thread::start { + let sched = ssched_cell.take(); + sched.run(); + }; + + // wait for the end + let _thread1 = normal_thread; + let _thread2 = special_thread; + + } + } + // The following test is a bit of a mess, but it trys to do // something tricky so I'm not sure how to get around this in the // short term. @@ -865,9 +1035,9 @@ mod test { // observe that the task is not home, and send it home. // This test is light in that it does very little. - + #[test] - fn test_transfer_task_home() { + fn test_transfer_task_home() { use rt::uv::uvio::UvEventLoop; use rt::sched::Shutdown; @@ -879,18 +1049,18 @@ mod test { use vec::OwnedVector; do run_in_bare_thread { - + static N: uint = 8; - + let sleepers = SleeperList::new(); let work_queue = WorkQueue::new(); - + let mut handles = ~[]; let mut scheds = ~[]; - + for uint::range(0, N) |_| { let loop_ = ~UvEventLoop::new(); - let mut sched = ~Scheduler::new(loop_, + let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); let handle = sched.make_handle(); @@ -901,7 +1071,7 @@ mod test { let handles = Cell(handles); - let home_handle = scheds[6].make_handle(); + let home_handle = scheds[6].make_handle(); let home_id = home_handle.sched_id; let home = Sched(home_handle); @@ -913,18 +1083,18 @@ mod test { sched.sched_id(), home_id); assert!(sched.sched_id() == home_id); - Local::put::(sched); + Local::put::(sched); let mut handles = handles.take(); for handles.each_mut |handle| { handle.send(Shutdown); } }; - + scheds[0].enqueue_task(main_task); - + let mut threads = ~[]; - + while !scheds.is_empty() { let sched = scheds.pop(); let sched_cell = Cell(sched); @@ -934,13 +1104,23 @@ mod test { }; threads.push(thread); } - + let _threads = threads; } } - + + // Do it a lot + + #[test] + fn test_stress_schedule_task_states() { + let n = stress_factor() * 120; + for int::range(0,n as int) |_| { + test_schedule_home_states(); + } + } + // The goal is that this is the high-stress test for making sure - // homing is working. It allocates 120*RUST_RT_STRESS tasks that + // homing is working. It allocates RUST_RT_STRESS tasks that // do nothing but assert that they are home at execution // time. These tasks are queued to random schedulers, so sometimes // they are home and sometimes not. It also runs RUST_RT_STRESS @@ -953,7 +1133,6 @@ mod test { run_in_mt_newsched_task_random_homed(); } } - #[test] fn test_simple_scheduling() { @@ -1210,8 +1389,8 @@ mod test { fn start_closure_dtor() { use ops::Drop; - // Regression test that the `start` task entrypoint can contain dtors - // that use task resources + // Regression test that the `start` task entrypoint can + // contain dtors that use task resources do run_in_newsched_task { struct S { field: () } @@ -1226,7 +1405,7 @@ mod test { do spawntask { let _ss = &s; } - } + } } } diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index 4d9851d3b409..06318ac6623b 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -19,6 +19,7 @@ use cast::transmute; use rt::local::Local; use super::local_heap::LocalHeap; use rt::logging::StdErrLogger; +use rt::sched::{SchedHome, AnySched}; pub struct Task { heap: LocalHeap, @@ -26,7 +27,8 @@ pub struct Task { storage: LocalStorage, logger: StdErrLogger, unwinder: Option, - destroyed: bool + destroyed: bool, + home: Option } pub struct GarbageCollector; @@ -44,7 +46,8 @@ impl Task { storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, unwinder: Some(Unwinder { unwinding: false }), - destroyed: false + destroyed: false, + home: Some(AnySched) } } @@ -55,10 +58,15 @@ impl Task { storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, unwinder: None, - destroyed: false + destroyed: false, + home: Some(AnySched) } } + pub fn give_home(&mut self, new_home: SchedHome) { + self.home = Some(new_home); + } + pub fn run(&mut self, f: &fn()) { // This is just an assertion that `run` was called unsafely // and this instance of Task is still accessible. diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index ecef505ce579..bb284c025417 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -162,18 +162,19 @@ pub fn run_in_mt_newsched_task_random_homed() { for uint::range(0, nthreads) |i| { let special = (i % 2) == 0; let loop_ = ~UvEventLoop::new(); - let mut sched = ~Scheduler::new_special(loop_, work_queue.clone(), sleepers.clone(), special); + let mut sched = ~Scheduler::new_special( + loop_, work_queue.clone(), sleepers.clone(), special); let handle = sched.make_handle(); handles.push(handle); scheds.push(sched); - } + } // Schedule a pile o tasks - let n = 5*stress_factor(); + let n = 5*stress_factor(); for uint::range(0,n) |_i| { rtdebug!("creating task: %u", _i); let hf: ~fn() = || { assert!(true) }; - spawntask_homed(&mut scheds, hf); + spawntask_homed(&mut scheds, hf); } // Now we want another pile o tasks that do not ever run on a @@ -182,11 +183,11 @@ pub fn run_in_mt_newsched_task_random_homed() { let n = 5*stress_factor(); - let f: ~fn() = || { + let f: ~fn() = || { for uint::range(0,n) |_| { - let f: ~fn() = || { + let f: ~fn() = || { // Borrow the scheduler we run on and check if it is - // privliged. + // privileged. do Local::borrow:: |sched| { assert!(sched.run_anything); }; @@ -194,12 +195,12 @@ pub fn run_in_mt_newsched_task_random_homed() { spawntask_random(f); }; }; - + let f_cell = Cell(f); let handles = Cell(handles); rtdebug!("creating main task"); - + let main_task = ~do Coroutine::new(&mut scheds[0].stack_pool) { f_cell.take()(); let mut handles = handles.take(); @@ -210,7 +211,7 @@ pub fn run_in_mt_newsched_task_random_homed() { }; rtdebug!("queuing main task") - + scheds[0].enqueue_task(main_task); let mut threads = ~[]; @@ -243,11 +244,13 @@ pub fn run_in_mt_newsched_task_random_homed() { pub fn spawntask(f: ~fn()) { use super::sched::*; + rtdebug!("spawntask taking the scheduler from TLS") let mut sched = Local::take::(); let task = ~Coroutine::with_task(&mut sched.stack_pool, ~Task::without_unwinding(), f); - sched.schedule_new_task(task); + rtdebug!("spawntask scheduling the new task"); + sched.schedule_task(task); } /// Create a new task and run it right now. Aborts on failure @@ -305,7 +308,7 @@ pub fn spawntask_homed(scheds: &mut ~[~Scheduler], f: ~fn()) { use super::sched::*; use rand::{rng, RngUtil}; let mut rng = rng(); - + let task = { let sched = &mut scheds[rng.gen_int_range(0,scheds.len() as int)]; let handle = sched.make_handle(); @@ -321,14 +324,15 @@ pub fn spawntask_homed(scheds: &mut ~[~Scheduler], f: ~fn()) { assert!(home_id == sched.sched_id()); }; f() - }; - + }; + ~Coroutine::with_task_homed(&mut sched.stack_pool, ~Task::without_unwinding(), af, Sched(handle)) }; let dest_sched = &mut scheds[rng.gen_int_range(0,scheds.len() as int)]; + // enqueue it for future execution dest_sched.enqueue_task(task); } diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs index 5941221821a8..5e507238f671 100644 --- a/src/libstd/task/spawn.rs +++ b/src/libstd/task/spawn.rs @@ -578,7 +578,7 @@ fn spawn_raw_newsched(_opts: TaskOpts, f: ~fn()) { let mut sched = Local::take::(); let task = ~Coroutine::new(&mut sched.stack_pool, f); - sched.schedule_new_task(task); + sched.schedule_task(task); } fn spawn_raw_oldsched(mut opts: TaskOpts, f: ~fn()) { From 505ef7e710ff890c0027fadad54997041b7ee93b Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 13 Jun 2013 23:31:19 -0700 Subject: [PATCH 42/55] std::rt: Tasks contain a JoinLatch --- src/libstd/rt/task.rs | 33 +++++++++++++++++++++++++- src/libstd/rt/test.rs | 55 +++++++++++++++++++------------------------ 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index 7c08dabf0bd8..75ca4c941c50 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -16,9 +16,11 @@ use prelude::*; use libc::{c_void, uintptr_t}; use cast::transmute; +use option::{Option, Some, None}; use rt::local::Local; use super::local_heap::LocalHeap; use rt::logging::StdErrLogger; +use rt::join_latch::JoinLatch; pub struct Task { heap: LocalHeap, @@ -26,6 +28,8 @@ pub struct Task { storage: LocalStorage, logger: StdErrLogger, unwinder: Unwinder, + join_latch: Option<~JoinLatch>, + on_exit: Option<~fn(bool)>, destroyed: bool } @@ -44,6 +48,8 @@ impl Task { storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, unwinder: Unwinder { unwinding: false }, + join_latch: Some(JoinLatch::new_root()), + on_exit: None, destroyed: false } } @@ -55,6 +61,8 @@ impl Task { storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, unwinder: Unwinder { unwinding: false }, + join_latch: Some(self.join_latch.get_mut_ref().new_child()), + on_exit: None, destroyed: false } } @@ -68,9 +76,22 @@ impl Task { self.unwinder.try(f); self.destroy(); + + // Wait for children. Possibly report the exit status. + let local_success = !self.unwinder.unwinding; + let join_latch = self.join_latch.swap_unwrap(); + match self.on_exit { + Some(ref on_exit) => { + let success = join_latch.wait(local_success); + (*on_exit)(success); + } + None => { + join_latch.release(local_success); + } + } } - /// Must be called manually before finalization to clean up + /// must be called manually before finalization to clean up /// thread-local resources. Some of the routines here expect /// Task to be available recursively so this must be /// called unsafely, without removing Task from @@ -216,5 +237,15 @@ mod test { assert!(port.recv() == 10); } } + + #[test] + fn linked_failure() { + do run_in_newsched_task() { + let res = do spawntask_try { + spawntask_random(|| fail!()); + }; + assert!(res.is_err()); + } + } } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index ecfe93560b4b..36e394e5c5bb 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -18,6 +18,7 @@ use vec::OwnedVector; use result::{Result, Ok, Err}; use unstable::run_in_bare_thread; use super::io::net::ip::{IpAddr, Ipv4}; +use rt::comm::oneshot; use rt::task::Task; use rt::thread::Thread; use rt::local::Local; @@ -47,8 +48,11 @@ pub fn run_in_newsched_task(f: ~fn()) { do run_in_bare_thread { let mut sched = ~new_test_uv_sched(); + let mut new_task = ~Task::new_root(); + let on_exit: ~fn(bool) = |exit_status| rtassert!(exit_status); + new_task.on_exit = Some(on_exit); let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::new_root(), + new_task, f.take()); sched.enqueue_task(task); sched.run(); @@ -94,16 +98,20 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let f_cell = Cell(f_cell.take()); let handles = Cell(handles); - let main_task = ~do Coroutine::new_root(&mut scheds[0].stack_pool) { - f_cell.take()(); + let mut new_task = ~Task::new_root(); + let on_exit: ~fn(bool) = |exit_status| { let mut handles = handles.take(); // Tell schedulers to exit for handles.each_mut |handle| { handle.send(Shutdown); } - }; + rtassert!(exit_status); + }; + new_task.on_exit = Some(on_exit); + let main_task = ~Coroutine::with_task(&mut scheds[0].stack_pool, + new_task, f_cell.take()); scheds[0].enqueue_task(main_task); let mut threads = ~[]; @@ -213,36 +221,21 @@ pub fn spawntask_random(f: ~fn()) { pub fn spawntask_try(f: ~fn()) -> Result<(), ()> { use cell::Cell; use super::sched::*; - use task; - use unstable::finally::Finally; - // Our status variables will be filled in from the scheduler context - let mut failed = false; - let failed_ptr: *mut bool = &mut failed; - - // Switch to the scheduler - let f = Cell(Cell(f)); - let sched = Local::take::(); - do sched.deschedule_running_task_and_then() |sched, old_task| { - let old_task = Cell(old_task); - let f = f.take(); - let new_task = ~do Coroutine::new_root(&mut sched.stack_pool) { - do (|| { - (f.take())() - }).finally { - // Check for failure then resume the parent task - unsafe { *failed_ptr = task::failing(); } - let sched = Local::take::(); - do sched.switch_running_tasks_and_then(old_task.take()) |sched, new_task| { - sched.enqueue_task(new_task); - } - } - }; - - sched.enqueue_task(new_task); + let (port, chan) = oneshot(); + let chan = Cell(chan); + let mut new_task = ~Task::new_root(); + let on_exit: ~fn(bool) = |exit_status| chan.take().send(exit_status); + new_task.on_exit = Some(on_exit); + let mut sched = Local::take::(); + let new_task = ~Coroutine::with_task(&mut sched.stack_pool, + new_task, f); + do sched.switch_running_tasks_and_then(new_task) |sched, old_task| { + sched.enqueue_task(old_task); } - if !failed { Ok(()) } else { Err(()) } + let exit_status = port.recv(); + if exit_status { Ok(()) } else { Err(()) } } // Spawn a new task in a new scheduler and return a thread handle. From 3281f5b63792a57d2cea6e93446e63f44e1e3ea0 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 17 Jun 2013 22:17:51 -0700 Subject: [PATCH 43/55] std::rt: Add util mod and num_cpus function --- src/libstd/rt/mod.rs | 3 +++ src/libstd/rt/test.rs | 7 ++----- src/libstd/rt/util.rs | 22 ++++++++++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 src/libstd/rt/util.rs diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 1724361cabcc..10b5c78f99e5 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -139,6 +139,9 @@ pub mod join_latch; pub mod metrics; +// FIXME #5248 shouldn't be pub +/// Just stuff +pub mod util; /// Set up a default runtime configuration, given compiler-supplied arguments. /// diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index 6e4fb9b1d940..36efcd91834b 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -63,11 +63,11 @@ pub fn run_in_newsched_task(f: ~fn()) { /// in one of the schedulers. The schedulers will stay alive /// until the function `f` returns. pub fn run_in_mt_newsched_task(f: ~fn()) { - use libc; use os; use from_str::FromStr; use rt::uv::uvio::UvEventLoop; use rt::sched::Shutdown; + use rt::util; let f_cell = Cell::new(f); @@ -78,7 +78,7 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { // Using more threads than cores in test code // to force the OS to preempt them frequently. // Assuming that this help stress test concurrent types. - rust_get_num_cpus() * 2 + util::num_cpus() * 2 } }; @@ -132,9 +132,6 @@ pub fn run_in_mt_newsched_task(f: ~fn()) { let _threads = threads; } - extern { - fn rust_get_num_cpus() -> libc::uintptr_t; - } } // THIS IS AWFUL. Copy-pasted the above initialization function but diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs new file mode 100644 index 000000000000..39b9de90f347 --- /dev/null +++ b/src/libstd/rt/util.rs @@ -0,0 +1,22 @@ +// Copyright 2013 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use libc; + +/// Get the number of cores available +pub fn num_cpus() -> uint { + unsafe { + return rust_get_num_cpus(); + } + + extern { + fn rust_get_num_cpus() -> libc::uintptr_t; + } +} \ No newline at end of file From 9ef4c413a869b4fc1e5df3f79f484b2ffd46cda0 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 17 Jun 2013 23:18:20 -0700 Subject: [PATCH 44/55] std::rt: Check exchange count on exit --- src/libstd/rt/global_heap.rs | 37 ++++++++++++++++++++++++++++++------ src/libstd/rt/mod.rs | 6 ++++++ 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/libstd/rt/global_heap.rs b/src/libstd/rt/global_heap.rs index ce7ff87b4458..9f273fc8e653 100644 --- a/src/libstd/rt/global_heap.rs +++ b/src/libstd/rt/global_heap.rs @@ -14,7 +14,7 @@ use c_malloc = libc::malloc; use c_free = libc::free; use managed::raw::{BoxHeaderRepr, BoxRepr}; use cast::transmute; -use unstable::intrinsics::{atomic_xadd,atomic_xsub}; +use unstable::intrinsics::{atomic_xadd,atomic_xsub, atomic_load}; use ptr::null; use intrinsic::TyDesc; @@ -34,8 +34,7 @@ pub unsafe fn malloc(td: *TypeDesc, size: uint) -> *c_void { box.header.prev = null(); box.header.next = null(); - let exchange_count = &mut *exchange_count_ptr(); - atomic_xadd(exchange_count, 1); + inc_count(); return transmute(box); } @@ -48,21 +47,47 @@ pub unsafe fn malloc_raw(size: uint) -> *c_void { if p.is_null() { fail!("Failure in malloc_raw: result ptr is null"); } + inc_count(); p } pub unsafe fn free(ptr: *c_void) { - let exchange_count = &mut *exchange_count_ptr(); - atomic_xsub(exchange_count, 1); - assert!(ptr.is_not_null()); + dec_count(); c_free(ptr); } ///Thin wrapper around libc::free, as with exchange_alloc::malloc_raw pub unsafe fn free_raw(ptr: *c_void) { + assert!(ptr.is_not_null()); + dec_count(); c_free(ptr); } +fn inc_count() { + unsafe { + let exchange_count = &mut *exchange_count_ptr(); + atomic_xadd(exchange_count, 1); + } +} + +fn dec_count() { + unsafe { + let exchange_count = &mut *exchange_count_ptr(); + atomic_xsub(exchange_count, 1); + } +} + +pub fn cleanup() { + unsafe { + let count_ptr = exchange_count_ptr(); + let allocations = atomic_load(&*count_ptr); + if allocations != 0 { + abort!("exchange heap not empty on exit\ + %i dangling allocations", allocations); + } + } +} + fn get_box_size(body_size: uint, body_align: uint) -> uint { let header_size = size_of::(); // FIXME (#2699): This alignment calculation is suspicious. Is it right? diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 10b5c78f99e5..0a269aa8767e 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -176,6 +176,8 @@ pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { sched.enqueue_task(main_task); sched.run(); + cleanup(); + return 0; } @@ -185,6 +187,10 @@ pub fn init(crate_map: *u8) { logging::init(crate_map); } +pub fn cleanup() { + global_heap::cleanup(); +} + /// Possible contexts in which Rust code may be executing. /// Different runtime services are available depending on context. /// Mostly used for determining if we're using the new scheduler From 021e81fbd311d93c94cbd40524ff53062fd7fe6e Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 17 Jun 2013 23:22:41 -0700 Subject: [PATCH 45/55] std::rt: move abort function to util module --- src/libstd/macros.rs | 10 +--------- src/libstd/rt/util.rs | 4 ++++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index 8d221fe6a1b7..51da9344cabd 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -39,18 +39,10 @@ macro_rules! rtassert ( ) -// The do_abort function was originally inside the abort macro, but -// this was ICEing the compiler so it has been moved outside. Now this -// seems to work? -#[allow(missing_doc)] -pub fn do_abort() -> ! { - unsafe { ::libc::abort(); } -} - macro_rules! abort( ($( $msg:expr),+) => ( { rtdebug!($($msg),+); - ::macros::do_abort(); + ::rt::util::abort(); } ) ) diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs index 39b9de90f347..6153170ecafe 100644 --- a/src/libstd/rt/util.rs +++ b/src/libstd/rt/util.rs @@ -19,4 +19,8 @@ pub fn num_cpus() -> uint { extern { fn rust_get_num_cpus() -> libc::uintptr_t; } +} + +pub fn abort() -> ! { + unsafe { libc::abort(); } } \ No newline at end of file From b5fbec9c1e519c7e6fa45e2157f6e746ef3f6849 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Mon, 17 Jun 2013 23:24:50 -0700 Subject: [PATCH 46/55] std: Rename `abort!` to `rtabort!` to match other macros --- src/libstd/macros.rs | 4 ++-- src/libstd/rt/global_heap.rs | 2 +- src/libstd/rt/local.rs | 24 ++++++++++++------------ src/libstd/rt/local_ptr.rs | 2 +- src/libstd/rt/sched.rs | 14 +++++++------- 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index 51da9344cabd..fed3ff461c44 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -33,13 +33,13 @@ macro_rules! rtdebug ( macro_rules! rtassert ( ( $arg:expr ) => ( { if !$arg { - abort!("assertion failed: %s", stringify!($arg)); + rtabort!("assertion failed: %s", stringify!($arg)); } } ) ) -macro_rules! abort( +macro_rules! rtabort( ($( $msg:expr),+) => ( { rtdebug!($($msg),+); ::rt::util::abort(); diff --git a/src/libstd/rt/global_heap.rs b/src/libstd/rt/global_heap.rs index 9f273fc8e653..b180cec98387 100644 --- a/src/libstd/rt/global_heap.rs +++ b/src/libstd/rt/global_heap.rs @@ -82,7 +82,7 @@ pub fn cleanup() { let count_ptr = exchange_count_ptr(); let allocations = atomic_load(&*count_ptr); if allocations != 0 { - abort!("exchange heap not empty on exit\ + rtabort!("exchange heap not empty on exit\ %i dangling allocations", allocations); } } diff --git a/src/libstd/rt/local.rs b/src/libstd/rt/local.rs index 6e0fbda5ec9a..6df1ffaa453f 100644 --- a/src/libstd/rt/local.rs +++ b/src/libstd/rt/local.rs @@ -38,17 +38,17 @@ impl Local for Scheduler { } match res { Some(r) => { r } - None => abort!("function failed!") + None => rtabort!("function failed!") } } unsafe fn unsafe_borrow() -> *mut Scheduler { local_ptr::unsafe_borrow() } - unsafe fn try_unsafe_borrow() -> Option<*mut Scheduler> { abort!("unimpl") } + unsafe fn try_unsafe_borrow() -> Option<*mut Scheduler> { rtabort!("unimpl") } } impl Local for Task { - fn put(_value: ~Task) { abort!("unimpl") } - fn take() -> ~Task { abort!("unimpl") } - fn exists() -> bool { abort!("unimpl") } + fn put(_value: ~Task) { rtabort!("unimpl") } + fn take() -> ~Task { rtabort!("unimpl") } + fn exists() -> bool { rtabort!("unimpl") } fn borrow(f: &fn(&mut Task) -> T) -> T { do Local::borrow:: |sched| { match sched.current_task { @@ -56,7 +56,7 @@ impl Local for Task { f(&mut *task.task) } None => { - abort!("no scheduler") + rtabort!("no scheduler") } } } @@ -69,7 +69,7 @@ impl Local for Task { } None => { // Don't fail. Infinite recursion - abort!("no scheduler") + rtabort!("no scheduler") } } } @@ -84,16 +84,16 @@ impl Local for Task { // XXX: This formulation won't work once ~IoFactoryObject is a real trait pointer impl Local for IoFactoryObject { - fn put(_value: ~IoFactoryObject) { abort!("unimpl") } - fn take() -> ~IoFactoryObject { abort!("unimpl") } - fn exists() -> bool { abort!("unimpl") } - fn borrow(_f: &fn(&mut IoFactoryObject) -> T) -> T { abort!("unimpl") } + fn put(_value: ~IoFactoryObject) { rtabort!("unimpl") } + fn take() -> ~IoFactoryObject { rtabort!("unimpl") } + fn exists() -> bool { rtabort!("unimpl") } + fn borrow(_f: &fn(&mut IoFactoryObject) -> T) -> T { rtabort!("unimpl") } unsafe fn unsafe_borrow() -> *mut IoFactoryObject { let sched = Local::unsafe_borrow::(); let io: *mut IoFactoryObject = (*sched).event_loop.io().unwrap(); return io; } - unsafe fn try_unsafe_borrow() -> Option<*mut IoFactoryObject> { abort!("unimpl") } + unsafe fn try_unsafe_borrow() -> Option<*mut IoFactoryObject> { rtabort!("unimpl") } } #[cfg(test)] diff --git a/src/libstd/rt/local_ptr.rs b/src/libstd/rt/local_ptr.rs index 0db903f81eec..cd7c5daa444d 100644 --- a/src/libstd/rt/local_ptr.rs +++ b/src/libstd/rt/local_ptr.rs @@ -109,7 +109,7 @@ pub unsafe fn unsafe_borrow() -> *mut T { fn tls_key() -> tls::Key { match maybe_tls_key() { Some(key) => key, - None => abort!("runtime tls key not initialized") + None => rtabort!("runtime tls key not initialized") } } diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index be57247d5147..5c0829880121 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -357,7 +357,7 @@ impl Scheduler { home_handle.send(PinnedTask(task)); } AnySched => { - abort!("error: cannot send anysched task home"); + rtabort!("error: cannot send anysched task home"); } } } @@ -422,10 +422,10 @@ impl Scheduler { return true; } 4 => { - abort!("task home was None!"); + rtabort!("task home was None!"); } _ => { - abort!("literally, you should not be here"); + rtabort!("literally, you should not be here"); } } } @@ -452,7 +452,7 @@ impl Scheduler { dead_task.take().recycle(&mut sched.stack_pool); } - abort!("control reached end of task"); + rtabort!("control reached end of task"); } pub fn schedule_task(~self, task: ~Coroutine) { @@ -672,7 +672,7 @@ impl Coroutine { Some(Sched(SchedHandle { sched_id: ref id, _ })) => { *id == sched.sched_id() } - None => { abort!("error: homeless task!"); } + None => { rtabort!("error: homeless task!"); } } } } @@ -696,7 +696,7 @@ impl Coroutine { match self.task.home { Some(AnySched) => { false } Some(Sched(_)) => { true } - None => { abort!("error: homeless task!"); + None => { rtabort!("error: homeless task!"); } } } @@ -710,7 +710,7 @@ impl Coroutine { Some(Sched(SchedHandle { sched_id: ref id, _})) => { *id == sched.sched_id() } - None => { abort!("error: homeless task!"); } + None => { rtabort!("error: homeless task!"); } } } From 5b2dc520340103491088616ba4f58095948f5821 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 18 Jun 2013 00:17:14 -0700 Subject: [PATCH 47/55] std::rt: Turn on multithreaded scheduling --- src/libstd/rt/mod.rs | 92 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 76 insertions(+), 16 deletions(-) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 0a269aa8767e..581e3addff0f 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -60,7 +60,21 @@ Several modules in `core` are clients of `rt`: #[deny(unused_variable)]; use cell::Cell; +use clone::Clone; +use container::Container; +use from_str::FromStr; +use iterator::IteratorUtil; +use option::{Some, None}; +use os; use ptr::RawPtr; +use uint; +use rt::sched::{Scheduler, Coroutine, Shutdown}; +use rt::sleeper_list::SleeperList; +use rt::task::Task; +use rt::thread::Thread; +use rt::work_queue::WorkQueue; +use rt::uv::uvio::UvEventLoop; +use vec::{OwnedVector, MutableVector}; /// The global (exchange) heap. pub mod global_heap; @@ -159,23 +173,8 @@ pub mod util; /// The return value is used as the process return code. 0 on success, 101 on error. pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { - use self::sched::{Scheduler, Coroutine}; - use self::work_queue::WorkQueue; - use self::uv::uvio::UvEventLoop; - use self::sleeper_list::SleeperList; - init(crate_map); - - let loop_ = ~UvEventLoop::new(); - let work_queue = WorkQueue::new(); - let sleepers = SleeperList::new(); - let mut sched = ~Scheduler::new(loop_, work_queue, sleepers); - sched.no_sleep = true; - let main_task = ~Coroutine::new_root(&mut sched.stack_pool, main); - - sched.enqueue_task(main_task); - sched.run(); - + run(main); cleanup(); return 0; @@ -191,6 +190,67 @@ pub fn cleanup() { global_heap::cleanup(); } +pub fn run(main: ~fn()) { + let nthreads = match os::getenv("RUST_THREADS") { + Some(nstr) => FromStr::from_str(nstr).get(), + None => unsafe { + // Using more threads than cores in test code + // to force the OS to preempt them frequently. + // Assuming that this help stress test concurrent types. + util::num_cpus() * 2 + } + }; + + let sleepers = SleeperList::new(); + let work_queue = WorkQueue::new(); + + let mut handles = ~[]; + let mut scheds = ~[]; + + for uint::range(0, nthreads) |_| { + let loop_ = ~UvEventLoop::new(); + let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); + let handle = sched.make_handle(); + + handles.push(handle); + scheds.push(sched); + } + + let main_cell = Cell::new(main); + let handles = Cell::new(handles); + let mut new_task = ~Task::new_root(); + let on_exit: ~fn(bool) = |exit_status| { + + let mut handles = handles.take(); + // Tell schedulers to exit + for handles.mut_iter().advance |handle| { + handle.send(Shutdown); + } + + rtassert!(exit_status); + }; + new_task.on_exit = Some(on_exit); + let main_task = ~Coroutine::with_task(&mut scheds[0].stack_pool, + new_task, main_cell.take()); + scheds[0].enqueue_task(main_task); + + let mut threads = ~[]; + + while !scheds.is_empty() { + let sched = scheds.pop(); + let sched_cell = Cell::new(sched); + let thread = do Thread::start { + let sched = sched_cell.take(); + sched.run(); + }; + + threads.push(thread); + } + + // Wait for schedulers + let _threads = threads; +} + /// Possible contexts in which Rust code may be executing. /// Different runtime services are available depending on context. /// Mostly used for determining if we're using the new scheduler From 29ad8e15a2b7e2024941d74ea4ce261cb501ded9 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Tue, 18 Jun 2013 14:23:37 -0700 Subject: [PATCH 48/55] std::rt: Improve the rtabort! macro --- src/libstd/macros.rs | 19 +++++------ src/libstd/rt/global_heap.rs | 3 +- src/libstd/rt/util.rs | 65 ++++++++++++++++++++++++++++++++++-- 3 files changed, 72 insertions(+), 15 deletions(-) diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index fed3ff461c44..7748c43efcd2 100644 --- a/src/libstd/macros.rs +++ b/src/libstd/macros.rs @@ -10,18 +10,16 @@ #[macro_escape]; +macro_rules! rterrln ( + ($( $arg:expr),+) => ( { + ::rt::util::dumb_println(fmt!( $($arg),+ )); + } ) +) + // Some basic logging macro_rules! rtdebug_ ( ($( $arg:expr),+) => ( { - dumb_println(fmt!( $($arg),+ )); - - fn dumb_println(s: &str) { - use io::WriterUtil; - let dbg = ::libc::STDERR_FILENO as ::io::fd_t; - dbg.write_str(s); - dbg.write_str("\n"); - } - + rterrln!( $($arg),+ ) } ) ) @@ -41,8 +39,7 @@ macro_rules! rtassert ( macro_rules! rtabort( ($( $msg:expr),+) => ( { - rtdebug!($($msg),+); - ::rt::util::abort(); + ::rt::util::abort(fmt!($($msg),+)); } ) ) diff --git a/src/libstd/rt/global_heap.rs b/src/libstd/rt/global_heap.rs index b180cec98387..e89df2b1c93f 100644 --- a/src/libstd/rt/global_heap.rs +++ b/src/libstd/rt/global_heap.rs @@ -82,8 +82,7 @@ pub fn cleanup() { let count_ptr = exchange_count_ptr(); let allocations = atomic_load(&*count_ptr); if allocations != 0 { - rtabort!("exchange heap not empty on exit\ - %i dangling allocations", allocations); + rtabort!("exchange heap not empty on exit - %i dangling allocations", allocations); } } } diff --git a/src/libstd/rt/util.rs b/src/libstd/rt/util.rs index 6153170ecafe..904b2f8bbb93 100644 --- a/src/libstd/rt/util.rs +++ b/src/libstd/rt/util.rs @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use container::Container; +use iterator::IteratorUtil; use libc; +use str::StrSlice; /// Get the number of cores available pub fn num_cpus() -> uint { @@ -21,6 +24,64 @@ pub fn num_cpus() -> uint { } } -pub fn abort() -> ! { +pub fn dumb_println(s: &str) { + use io::WriterUtil; + let dbg = ::libc::STDERR_FILENO as ::io::fd_t; + dbg.write_str(s); + dbg.write_str("\n"); +} + +pub fn abort(msg: &str) -> ! { + let msg = if !msg.is_empty() { msg } else { "aborted" }; + let hash = msg.iter().fold(0, |accum, val| accum + (val as uint) ); + let quote = match hash % 10 { + 0 => " +It was from the artists and poets that the pertinent answers came, and I +know that panic would have broken loose had they been able to compare notes. +As it was, lacking their original letters, I half suspected the compiler of +having asked leading questions, or of having edited the correspondence in +corroboration of what he had latently resolved to see.", + 1 => " +There are not many persons who know what wonders are opened to them in the +stories and visions of their youth; for when as children we listen and dream, +we think but half-formed thoughts, and when as men we try to remember, we are +dulled and prosaic with the poison of life. But some of us awake in the night +with strange phantasms of enchanted hills and gardens, of fountains that sing +in the sun, of golden cliffs overhanging murmuring seas, of plains that stretch +down to sleeping cities of bronze and stone, and of shadowy companies of heroes +that ride caparisoned white horses along the edges of thick forests; and then +we know that we have looked back through the ivory gates into that world of +wonder which was ours before we were wise and unhappy.", + 2 => " +Instead of the poems I had hoped for, there came only a shuddering blackness +and ineffable loneliness; and I saw at last a fearful truth which no one had +ever dared to breathe before — the unwhisperable secret of secrets — The fact +that this city of stone and stridor is not a sentient perpetuation of Old New +York as London is of Old London and Paris of Old Paris, but that it is in fact +quite dead, its sprawling body imperfectly embalmed and infested with queer +animate things which have nothing to do with it as it was in life.", + 3 => " +The ocean ate the last of the land and poured into the smoking gulf, thereby +giving up all it had ever conquered. From the new-flooded lands it flowed +again, uncovering death and decay; and from its ancient and immemorial bed it +trickled loathsomely, uncovering nighted secrets of the years when Time was +young and the gods unborn. Above the waves rose weedy remembered spires. The +moon laid pale lilies of light on dead London, and Paris stood up from its damp +grave to be sanctified with star-dust. Then rose spires and monoliths that were +weedy but not remembered; terrible spires and monoliths of lands that men never +knew were lands...", + 4 => " +There was a night when winds from unknown spaces whirled us irresistibly into +limitless vacum beyond all thought and entity. Perceptions of the most +maddeningly untransmissible sort thronged upon us; perceptions of infinity +which at the time convulsed us with joy, yet which are now partly lost to my +memory and partly incapable of presentation to others.", + _ => "You've met with a terrible fate, haven't you?" + }; + rterrln!("%s", ""); + rterrln!("%s", quote); + rterrln!("%s", ""); + rterrln!("fatal runtime error: %s", msg); + unsafe { libc::abort(); } -} \ No newline at end of file +} From 915aaa7f67671186348b1b6c10d765a3d9ab6e37 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 19 Jun 2013 00:39:10 -0700 Subject: [PATCH 49/55] std::rt: Set the process exit code --- src/libstd/rt/mod.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 581e3addff0f..899fa171b727 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -74,6 +74,8 @@ use rt::task::Task; use rt::thread::Thread; use rt::work_queue::WorkQueue; use rt::uv::uvio::UvEventLoop; +use unstable::atomics::{AtomicInt, SeqCst}; +use unstable::sync::UnsafeAtomicRcBox; use vec::{OwnedVector, MutableVector}; /// The global (exchange) heap. @@ -174,10 +176,10 @@ pub mod util; pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { init(crate_map); - run(main); + let exit_code = run(main); cleanup(); - return 0; + return exit_code; } /// One-time runtime initialization. Currently all this does is set up logging @@ -190,7 +192,9 @@ pub fn cleanup() { global_heap::cleanup(); } -pub fn run(main: ~fn()) { +pub fn run(main: ~fn()) -> int { + static DEFAULT_ERROR_CODE: int = 101; + let nthreads = match os::getenv("RUST_THREADS") { Some(nstr) => FromStr::from_str(nstr).get(), None => unsafe { @@ -216,10 +220,13 @@ pub fn run(main: ~fn()) { scheds.push(sched); } + let exit_code = UnsafeAtomicRcBox::new(AtomicInt::new(0)); + let exit_code_clone = exit_code.clone(); + let main_cell = Cell::new(main); let handles = Cell::new(handles); let mut new_task = ~Task::new_root(); - let on_exit: ~fn(bool) = |exit_status| { + let on_exit: ~fn(bool) = |exit_success| { let mut handles = handles.take(); // Tell schedulers to exit @@ -227,7 +234,10 @@ pub fn run(main: ~fn()) { handle.send(Shutdown); } - rtassert!(exit_status); + unsafe { + let exit_code = if exit_success { 0 } else { DEFAULT_ERROR_CODE }; + (*exit_code_clone.get()).store(exit_code, SeqCst); + } }; new_task.on_exit = Some(on_exit); let main_task = ~Coroutine::with_task(&mut scheds[0].stack_pool, @@ -249,6 +259,10 @@ pub fn run(main: ~fn()) { // Wait for schedulers let _threads = threads; + + unsafe { + (*exit_code.get()).load(SeqCst) + } } /// Possible contexts in which Rust code may be executing. From 5722c953e5180ae3e086b4354f65ee8b5fb8d868 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 19 Jun 2013 00:49:05 -0700 Subject: [PATCH 50/55] std::rt: Correct the numbers of default cores --- src/libstd/rt/mod.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 899fa171b727..58da7549b3f2 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -197,12 +197,7 @@ pub fn run(main: ~fn()) -> int { let nthreads = match os::getenv("RUST_THREADS") { Some(nstr) => FromStr::from_str(nstr).get(), - None => unsafe { - // Using more threads than cores in test code - // to force the OS to preempt them frequently. - // Assuming that this help stress test concurrent types. - util::num_cpus() * 2 - } + None => unsafe { util::num_cpus() } }; let sleepers = SleeperList::new(); From e1555f9b5628af2b6c6ed344cad621399cb7684d Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 19 Jun 2013 01:08:47 -0700 Subject: [PATCH 51/55] std::rt: Document and cleanup the run function --- src/libstd/rt/mod.rs | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index 58da7549b3f2..dd4c71eca745 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -63,11 +63,11 @@ use cell::Cell; use clone::Clone; use container::Container; use from_str::FromStr; +use iter::Times; use iterator::IteratorUtil; use option::{Some, None}; use os; use ptr::RawPtr; -use uint; use rt::sched::{Scheduler, Coroutine, Shutdown}; use rt::sleeper_list::SleeperList; use rt::task::Task; @@ -150,7 +150,7 @@ pub mod local_ptr; /// Bindings to pthread/windows thread-local storage. pub mod thread_local_storage; -/// A concurrent data structure with which parent tasks wait on child tasks. +/// For waiting on child tasks. pub mod join_latch; pub mod metrics; @@ -188,11 +188,18 @@ pub fn init(crate_map: *u8) { logging::init(crate_map); } +/// One-time runtime cleanup. pub fn cleanup() { global_heap::cleanup(); } +/// Execute the main function in a scheduler. +/// +/// Configures the runtime according to the environment, by default +/// using a task scheduler with the same number of threads as cores. +/// Returns a process exit code. pub fn run(main: ~fn()) -> int { + static DEFAULT_ERROR_CODE: int = 101; let nthreads = match os::getenv("RUST_THREADS") { @@ -200,31 +207,39 @@ pub fn run(main: ~fn()) -> int { None => unsafe { util::num_cpus() } }; + // The shared list of sleeping schedulers. Schedulers wake each other + // occassionally to do new work. let sleepers = SleeperList::new(); + // The shared work queue. Temporary until work stealing is implemented. let work_queue = WorkQueue::new(); - let mut handles = ~[]; + // The schedulers. let mut scheds = ~[]; + // Handles to the schedulers. When the main task ends these will be + // sent the Shutdown message to terminate the schedulers. + let mut handles = ~[]; - for uint::range(0, nthreads) |_| { + for nthreads.times { + // Every scheduler is driven by an I/O event loop. let loop_ = ~UvEventLoop::new(); let mut sched = ~Scheduler::new(loop_, work_queue.clone(), sleepers.clone()); let handle = sched.make_handle(); - handles.push(handle); scheds.push(sched); + handles.push(handle); } + // Create a shared cell for transmitting the process exit + // code from the main task to this function. let exit_code = UnsafeAtomicRcBox::new(AtomicInt::new(0)); let exit_code_clone = exit_code.clone(); - let main_cell = Cell::new(main); + // When the main task exits, after all the tasks in the main + // task tree, shut down the schedulers and set the exit code. let handles = Cell::new(handles); - let mut new_task = ~Task::new_root(); let on_exit: ~fn(bool) = |exit_success| { let mut handles = handles.take(); - // Tell schedulers to exit for handles.mut_iter().advance |handle| { handle.send(Shutdown); } @@ -234,13 +249,17 @@ pub fn run(main: ~fn()) -> int { (*exit_code_clone.get()).store(exit_code, SeqCst); } }; + + // Create and enqueue the main task. + let main_cell = Cell::new(main); + let mut new_task = ~Task::new_root(); new_task.on_exit = Some(on_exit); let main_task = ~Coroutine::with_task(&mut scheds[0].stack_pool, new_task, main_cell.take()); scheds[0].enqueue_task(main_task); + // Run each scheduler in a thread. let mut threads = ~[]; - while !scheds.is_empty() { let sched = scheds.pop(); let sched_cell = Cell::new(sched); @@ -253,8 +272,9 @@ pub fn run(main: ~fn()) -> int { } // Wait for schedulers - let _threads = threads; + { let _threads = threads; } + // Return the exit code unsafe { (*exit_code.get()).load(SeqCst) } From 753b497b4e7f5445bd5781572568b2b5cf0ce67d Mon Sep 17 00:00:00 2001 From: toddaaro Date: Wed, 19 Jun 2013 15:23:14 -0700 Subject: [PATCH 52/55] Modified a match in resume_task_from_queue that was returning an int that was then matched on to instead use an enum. --- src/libstd/rt/sched.rs | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 5c0829880121..453d3303db66 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -381,52 +381,44 @@ impl Scheduler { match home { &Some(Sched(ref home_handle)) if home_handle.sched_id != this.sched_id() => { - 0 + SendHome } &Some(AnySched) if this.run_anything => { - 1 + ResumeNow } &Some(AnySched) => { - 2 + Requeue } &Some(Sched(_)) => { - 3 + ResumeNow } &None => { - 4 + Homeless } } }; match action_id { - 0 => { + SendHome => { rtdebug!("sending task home"); Scheduler::send_task_home(task); Local::put(this); return false; } - 1 => { + ResumeNow => { rtdebug!("resuming now"); this.resume_task_immediately(task); return true; } - 2 => { + Requeue => { rtdebug!("re-queueing") this.enqueue_task(task); Local::put(this); return false; } - 3 => { - rtdebug!("resuming now"); - this.resume_task_immediately(task); - return true; - } - 4 => { + Homeless => { rtabort!("task home was None!"); } - _ => { - rtabort!("literally, you should not be here"); - } } } @@ -654,6 +646,14 @@ impl Scheduler { } } +// The cases for the below function. +enum ResumeAction { + SendHome, + Requeue, + ResumeNow, + Homeless +} + impl SchedHandle { pub fn send(&mut self, msg: SchedMessage) { self.queue.push(msg); From 5086c0850ebdd8407901d108f312ab141e4a4a18 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 19 Jun 2013 16:08:07 -0700 Subject: [PATCH 53/55] std::rt: Update GC metadata in init --- src/libstd/rt/mod.rs | 5 +++++ src/rt/rust_gc_metadata.cpp | 5 +++++ src/rt/rustrt.def.in | 1 + 3 files changed, 11 insertions(+) diff --git a/src/libstd/rt/mod.rs b/src/libstd/rt/mod.rs index dd4c71eca745..a80fb15bad79 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -186,6 +186,11 @@ pub fn start(_argc: int, _argv: **u8, crate_map: *u8, main: ~fn()) -> int { /// based on the RUST_LOG environment variable. pub fn init(crate_map: *u8) { logging::init(crate_map); + unsafe { rust_update_gc_metadata(crate_map) } + + extern { + fn rust_update_gc_metadata(crate_map: *u8); + } } /// One-time runtime cleanup. diff --git a/src/rt/rust_gc_metadata.cpp b/src/rt/rust_gc_metadata.cpp index fbf0575b31dc..e37856255a7d 100644 --- a/src/rt/rust_gc_metadata.cpp +++ b/src/rt/rust_gc_metadata.cpp @@ -79,6 +79,11 @@ rust_gc_metadata() { return (void *)global_safe_points; } +extern "C" CDECL void +rust_update_gc_metadata(const void* map) { + update_gc_metadata(map); +} + // // Local Variables: // mode: C++ diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index 9b49583519ec..c93d29f6148c 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -178,6 +178,7 @@ rust_call_tydesc_glue tdefl_compress_mem_to_heap tinfl_decompress_mem_to_heap rust_gc_metadata +rust_update_gc_metadata rust_uv_ip4_port rust_uv_ip6_port rust_uv_tcp_getpeername From 391bb0b4e7131cd7d30e03deea3eb9756a7c8954 Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Wed, 19 Jun 2013 18:37:50 -0700 Subject: [PATCH 54/55] std: Make newsched failures log correctly --- src/libstd/sys.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libstd/sys.rs b/src/libstd/sys.rs index e49ad3485420..f2591996e3a9 100644 --- a/src/libstd/sys.rs +++ b/src/libstd/sys.rs @@ -180,10 +180,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 option::Option; + use either::Left; use rt::{context, OldTaskContext, TaskContext}; use rt::task::{Task, Unwinder}; use rt::local::Local; + use rt::logging::Logger; let context = context(); match context { @@ -200,12 +203,18 @@ 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!("%s at line %i of file %s", msg, line as int, file); + let outmsg = fmt!("task failed: '%s' at line %i of file %s", + msg, line as int, file); // XXX: Logging doesn't work correctly in non-task context because it // invokes the local heap if context == TaskContext { - error!(outmsg); + // 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())); + } } else { rtdebug!("%s", outmsg); } From 4d39253a9623ff30c27cee3c9770634a41f4412d Mon Sep 17 00:00:00 2001 From: Brian Anderson Date: Thu, 20 Jun 2013 12:16:04 -0700 Subject: [PATCH 55/55] std::rt: Whitespace --- src/libstd/rt/sched.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 453d3303db66..bbe4aa25e296 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -646,12 +646,12 @@ impl Scheduler { } } -// The cases for the below function. +// The cases for the below function. enum ResumeAction { SendHome, Requeue, ResumeNow, - Homeless + Homeless } impl SchedHandle {