diff --git a/src/libextra/test.rs b/src/libextra/test.rs index 97793ce440c2b..1c6e2a25c01b7 100644 --- a/src/libextra/test.rs +++ b/src/libextra/test.rs @@ -36,14 +36,6 @@ use std::u64; use std::uint; use std::vec; -pub mod rustrt { - use std::libc::size_t; - - #[abi = "cdecl"] - pub extern { - pub unsafe fn rust_sched_threads() -> size_t; - } -} // The name of a test. By convention this follows the rules for rust // paths; i.e. it should be a series of identifiers separated by double @@ -493,11 +485,10 @@ static SCHED_OVERCOMMIT : uint = 1; static SCHED_OVERCOMMIT : uint = 4u; fn get_concurrency() -> uint { - unsafe { - let threads = rustrt::rust_sched_threads() as uint; - if threads == 1 { 1 } - else { threads * SCHED_OVERCOMMIT } - } + use std::rt; + let threads = rt::util::default_sched_threads(); + if threads == 1 { 1 } + else { threads * SCHED_OVERCOMMIT } } #[allow(non_implicitly_copyable_typarams)] diff --git a/src/libstd/at_vec.rs b/src/libstd/at_vec.rs index 37a32d6b0f008..dfd39af093d96 100644 --- a/src/libstd/at_vec.rs +++ b/src/libstd/at_vec.rs @@ -22,23 +22,6 @@ use vec::{ImmutableVector, OwnedVector}; /// Code for dealing with @-vectors. This is pretty incomplete, and /// contains a bunch of duplication from the code for ~-vectors. -pub mod rustrt { - use libc; - use vec; - #[cfg(stage0)] - use intrinsic::{TyDesc}; - #[cfg(not(stage0))] - use unstable::intrinsics::{TyDesc}; - - #[abi = "cdecl"] - #[link_name = "rustrt"] - pub extern { - pub unsafe fn vec_reserve_shared_actual(t: *TyDesc, - v: **vec::raw::VecRepr, - n: libc::size_t); - } -} - /// Returns the number of elements the vector can hold without reallocating #[inline] pub fn capacity(v: @[T]) -> uint { @@ -192,18 +175,17 @@ pub mod traits { pub mod traits {} pub mod raw { - use at_vec::{capacity, rustrt}; + use at_vec::capacity; + use cast; use cast::{transmute, transmute_copy}; use libc; use ptr; use sys; use uint; - use unstable::intrinsics::{move_val_init}; + use unstable::intrinsics; + use unstable::intrinsics::{move_val_init, TyDesc}; use vec; - #[cfg(stage0)] - use intrinsic::{get_tydesc}; - #[cfg(not(stage0))] - use unstable::intrinsics::{get_tydesc}; + use vec::UnboxedVecRepr; pub type VecRepr = vec::raw::VecRepr; pub type SliceRepr = vec::raw::SliceRepr; @@ -264,9 +246,49 @@ pub mod raw { pub unsafe fn reserve(v: &mut @[T], n: uint) { // Only make the (slow) call into the runtime if we have to if capacity(*v) < n { - let ptr: **VecRepr = transmute(v); - rustrt::vec_reserve_shared_actual(get_tydesc::(), - ptr, n as libc::size_t); + let ptr: *mut *mut VecRepr = transmute(v); + let ty = intrinsics::get_tydesc::(); + // XXX transmute shouldn't be necessary + let ty = cast::transmute(ty); + return reserve_raw(ty, ptr, n); + } + } + + // Implementation detail. Shouldn't be public + #[allow(missing_doc)] + pub fn reserve_raw(ty: *TyDesc, ptr: *mut *mut VecRepr, n: uint) { + + unsafe { + let size_in_bytes = n * (*ty).size; + if size_in_bytes > (**ptr).unboxed.alloc { + let total_size = size_in_bytes + sys::size_of::(); + // XXX: UnboxedVecRepr has an extra u8 at the end + let total_size = total_size - sys::size_of::(); + (*ptr) = local_realloc(*ptr as *(), total_size) as *mut VecRepr; + (**ptr).unboxed.alloc = size_in_bytes; + } + } + + fn local_realloc(ptr: *(), size: uint) -> *() { + use rt; + use rt::OldTaskContext; + use rt::local::Local; + use rt::task::Task; + + if rt::context() == OldTaskContext { + unsafe { + return rust_local_realloc(ptr, size as libc::size_t); + } + + extern { + #[fast_ffi] + fn rust_local_realloc(ptr: *(), size: libc::size_t) -> *(); + } + } else { + do Local::borrow:: |task| { + task.heap.realloc(ptr as *libc::c_void, size) as *() + } + } } } diff --git a/src/libstd/cleanup.rs b/src/libstd/cleanup.rs index ee9fdd3c62005..abda76c9ca660 100644 --- a/src/libstd/cleanup.rs +++ b/src/libstd/cleanup.rs @@ -10,105 +10,13 @@ #[doc(hidden)]; -use libc::{c_char, intptr_t, uintptr_t}; +use libc::c_void; use ptr::{mut_null}; use repr::BoxRepr; use cast::transmute; use unstable::intrinsics::TyDesc; -#[cfg(not(test))] use unstable::lang::clear_task_borrow_list; -/** - * Runtime structures - * - * NB: These must match the representation in the C++ runtime. - */ - -type TaskID = uintptr_t; - -struct StackSegment { priv opaque: () } -struct Scheduler { priv opaque: () } -struct SchedulerLoop { priv opaque: () } -struct Kernel { priv opaque: () } -struct Env { priv opaque: () } -struct AllocHeader { priv opaque: () } -struct MemoryRegion { priv opaque: () } - -#[cfg(target_arch="x86")] -struct Registers { - data: [u32, ..16] -} - -#[cfg(target_arch="arm")] -#[cfg(target_arch="mips")] -struct Registers { - data: [u32, ..32] -} - -#[cfg(target_arch="x86")] -#[cfg(target_arch="arm")] -#[cfg(target_arch="mips")] -struct Context { - regs: Registers, - next: *Context, - pad: [u32, ..3] -} - -#[cfg(target_arch="x86_64")] -struct Registers { - data: [u64, ..22] -} - -#[cfg(target_arch="x86_64")] -struct Context { - regs: Registers, - next: *Context, - pad: uintptr_t -} - -struct BoxedRegion { - env: *Env, - backing_region: *MemoryRegion, - live_allocs: *BoxRepr -} - -#[cfg(target_arch="x86")] -#[cfg(target_arch="arm")] -#[cfg(target_arch="mips")] -struct Task { - // Public fields - refcount: intptr_t, // 0 - id: TaskID, // 4 - pad: [u32, ..2], // 8 - ctx: Context, // 16 - stack_segment: *StackSegment, // 96 - runtime_sp: uintptr_t, // 100 - scheduler: *Scheduler, // 104 - scheduler_loop: *SchedulerLoop, // 108 - - // Fields known only to the runtime - kernel: *Kernel, // 112 - name: *c_char, // 116 - list_index: i32, // 120 - boxed_region: BoxedRegion // 128 -} - -#[cfg(target_arch="x86_64")] -struct Task { - // Public fields - refcount: intptr_t, - id: TaskID, - ctx: Context, - stack_segment: *StackSegment, - runtime_sp: uintptr_t, - scheduler: *Scheduler, - scheduler_loop: *SchedulerLoop, - - // Fields known only to the runtime - kernel: *Kernel, - name: *c_char, - list_index: i32, - boxed_region: BoxedRegion -} +type DropGlue<'self> = &'self fn(**TyDesc, *c_void); /* * Box annihilation @@ -127,9 +35,9 @@ unsafe fn each_live_alloc(read_next_before: bool, //! Walks the internal list of allocations use managed; + use rt::local_heap; - let task: *Task = transmute(rustrt::rust_get_task()); - let box = (*task).boxed_region.live_allocs; + let box = local_heap::live_allocs(); let mut box: *mut BoxRepr = transmute(copy box); while box != mut_null() { let next_before = transmute(copy (*box).header.next); @@ -151,7 +59,13 @@ unsafe fn each_live_alloc(read_next_before: bool, #[cfg(unix)] fn debug_mem() -> bool { - ::rt::env::get().debug_mem + use rt; + use rt::OldTaskContext; + // XXX: Need to port the environment struct to newsched + match rt::context() { + OldTaskContext => ::rt::env::get().debug_mem, + _ => false + } } #[cfg(windows)] @@ -173,13 +87,12 @@ unsafe fn call_drop_glue(tydesc: *TyDesc, data: *i8) { } /// Destroys all managed memory (i.e. @ boxes) held by the current task. -#[cfg(not(test))] -#[lang="annihilate"] pub unsafe fn annihilate() { - use unstable::lang::local_free; + use rt::local_heap::local_free; use io::WriterUtil; use io; use libc; + use rt::borrowck; use sys; use managed; @@ -191,7 +104,7 @@ pub unsafe fn annihilate() { // Quick hack: we need to free this list upon task exit, and this // is a convenient place to do it. - clear_task_borrow_list(); + borrowck::clear_task_borrow_list(); // Pass 1: Make all boxes immortal. // @@ -213,7 +126,7 @@ pub unsafe fn annihilate() { // callback, as the original value may have been freed. for each_live_alloc(false) |box, uniq| { if !uniq { - let tydesc = (*box).header.type_desc; + let tydesc: *TyDesc = transmute(copy (*box).header.type_desc); let data = transmute(&(*box).data); call_drop_glue(tydesc, data); } diff --git a/src/libstd/comm.rs b/src/libstd/comm.rs index 8316a33ecf1f8..1bb0ff044fe98 100644 --- a/src/libstd/comm.rs +++ b/src/libstd/comm.rs @@ -220,7 +220,7 @@ impl Peekable for PortSet { /// A channel that can be shared between many senders. pub struct SharedChan { - ch: Exclusive> + inner: Either>, rtcomm::SharedChan> } impl SharedChan { @@ -228,40 +228,50 @@ impl SharedChan { pub fn new(c: Chan) -> SharedChan { let Chan { inner } = c; let c = match inner { - Left(c) => c, - Right(_) => fail!("SharedChan not implemented") + Left(c) => Left(exclusive(c)), + Right(c) => Right(rtcomm::SharedChan::new(c)) }; - SharedChan { ch: exclusive(c) } + SharedChan { inner: c } } } impl GenericChan for SharedChan { fn send(&self, x: T) { - unsafe { - let mut xx = Some(x); - do self.ch.with_imm |chan| { - let x = replace(&mut xx, None); - chan.send(x.unwrap()) + match self.inner { + Left(ref chan) => { + unsafe { + let mut xx = Some(x); + do chan.with_imm |chan| { + let x = replace(&mut xx, None); + chan.send(x.unwrap()) + } + } } + Right(ref chan) => chan.send(x) } } } impl GenericSmartChan for SharedChan { fn try_send(&self, x: T) -> bool { - unsafe { - let mut xx = Some(x); - do self.ch.with_imm |chan| { - let x = replace(&mut xx, None); - chan.try_send(x.unwrap()) + match self.inner { + Left(ref chan) => { + unsafe { + let mut xx = Some(x); + do chan.with_imm |chan| { + let x = replace(&mut xx, None); + chan.try_send(x.unwrap()) + } + } } + Right(ref chan) => chan.try_send(x) } } } impl ::clone::Clone for SharedChan { fn clone(&self) -> SharedChan { - SharedChan { ch: self.ch.clone() } + SharedChan { inner: self.inner.clone() } } } diff --git a/src/libstd/logging.rs b/src/libstd/logging.rs index c2f854179b8dd..743b71e33ea80 100644 --- a/src/libstd/logging.rs +++ b/src/libstd/logging.rs @@ -11,13 +11,20 @@ //! Logging use option::*; +use os; use either::*; +use rt; +use rt::OldTaskContext; use rt::logging::{Logger, StdErrLogger}; /// Turns on logging to stdout globally pub fn console_on() { - unsafe { - rustrt::rust_log_console_on(); + if rt::context() == OldTaskContext { + unsafe { + rustrt::rust_log_console_on(); + } + } else { + rt::logging::console_on(); } } @@ -29,8 +36,17 @@ pub fn console_on() { * the RUST_LOG environment variable */ pub fn console_off() { - unsafe { - rustrt::rust_log_console_off(); + // If RUST_LOG is set then the console can't be turned off + if os::getenv("RUST_LOG").is_some() { + return; + } + + if rt::context() == OldTaskContext { + unsafe { + rustrt::rust_log_console_off(); + } + } else { + rt::logging::console_off(); } } diff --git a/src/libstd/macros.rs b/src/libstd/macros.rs index fda48b6ffb7d9..7748c43efcd28 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),+ ) } ) ) @@ -33,21 +31,15 @@ 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( - ($( $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(); } - } +macro_rules! rtabort( + ($( $msg:expr),+) => ( { + ::rt::util::abort(fmt!($($msg),+)); } ) ) + diff --git a/src/libstd/os.rs b/src/libstd/os.rs index be0c504885b31..5c78b7c14e533 100644 --- a/src/libstd/os.rs +++ b/src/libstd/os.rs @@ -740,6 +740,7 @@ pub fn list_dir(p: &Path) -> ~[~str] { as_utf16_p }; use rt::global_heap::malloc_raw; + #[nolink] extern { unsafe fn rust_list_dir_wfd_size() -> libc::size_t; @@ -1133,8 +1134,15 @@ pub fn last_os_error() -> ~str { * ignored and the process exits with the default failure status */ pub fn set_exit_status(code: int) { - unsafe { - rustrt::rust_set_exit_status(code as libc::intptr_t); + use rt; + use rt::OldTaskContext; + + if rt::context() == OldTaskContext { + unsafe { + rustrt::rust_set_exit_status(code as libc::intptr_t); + } + } else { + rt::util::set_exit_status(code); } } @@ -1164,10 +1172,20 @@ pub fn real_args() -> ~[~str] { #[cfg(target_os = "android")] #[cfg(target_os = "freebsd")] pub fn real_args() -> ~[~str] { - unsafe { - let argc = rustrt::rust_get_argc(); - let argv = rustrt::rust_get_argv(); - load_argc_and_argv(argc, argv) + use rt; + use rt::TaskContext; + + if rt::context() == TaskContext { + match rt::args::clone() { + Some(args) => args, + None => fail!("process arguments not initialized") + } + } else { + unsafe { + let argc = rustrt::rust_get_argc(); + let argv = rustrt::rust_get_argv(); + load_argc_and_argv(argc, argv) + } } } diff --git a/src/libstd/rt/args.rs b/src/libstd/rt/args.rs new file mode 100644 index 0000000000000..75ee4f381f6ef --- /dev/null +++ b/src/libstd/rt/args.rs @@ -0,0 +1,125 @@ +// Copyright 2012-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. + +//! Global storage for command line arguments +//! +//! The current incarnation of the Rust runtime expects for +//! the processes `argc` and `argv` arguments to be stored +//! in a globally-accessible location for use by the `os` module. +//! +//! XXX: Would be nice for this to not exist. +//! XXX: This has a lot of C glue for lack of globals. + +use libc; +use option::{Option, Some, None}; +use str; +use uint; +use unstable::finally::Finally; +use util; + +/// One-time global initialization. +pub unsafe fn init(argc: int, argv: **u8) { + let args = load_argc_and_argv(argc, argv); + put(args); +} + +/// One-time global cleanup. +pub fn cleanup() { + rtassert!(take().is_some()); +} + +/// Take the global arguments from global storage. +pub fn take() -> Option<~[~str]> { + with_lock(|| unsafe { + let ptr = get_global_ptr(); + let val = util::replace(&mut *ptr, None); + val.map(|s: &~~[~str]| (**s).clone()) + }) +} + +/// Give the global arguments to global storage. +/// +/// It is an error if the arguments already exist. +pub fn put(args: ~[~str]) { + with_lock(|| unsafe { + let ptr = get_global_ptr(); + rtassert!((*ptr).is_none()); + (*ptr) = Some(~args.clone()); + }) +} + +/// Make a clone of the global arguments. +pub fn clone() -> Option<~[~str]> { + with_lock(|| unsafe { + let ptr = get_global_ptr(); + (*ptr).map(|s: &~~[~str]| (**s).clone()) + }) +} + +fn with_lock(f: &fn() -> T) -> T { + do (|| { + unsafe { + rust_take_global_args_lock(); + f() + } + }).finally { + unsafe { + rust_drop_global_args_lock(); + } + } +} + +fn get_global_ptr() -> *mut Option<~~[~str]> { + unsafe { rust_get_global_args_ptr() } +} + +// Copied from `os`. +unsafe fn load_argc_and_argv(argc: int, argv: **u8) -> ~[~str] { + let mut args = ~[]; + for uint::range(0, argc as uint) |i| { + args.push(str::raw::from_c_str(*(argv as **libc::c_char).offset(i))); + } + return args; +} + +extern { + fn rust_take_global_args_lock(); + fn rust_drop_global_args_lock(); + fn rust_get_global_args_ptr() -> *mut Option<~~[~str]>; +} + +#[cfg(test)] +mod tests { + use option::{Some, None}; + use super::*; + use unstable::finally::Finally; + + #[test] + fn smoke_test() { + // Preserve the actual global state. + let saved_value = take(); + + let expected = ~[~"happy", ~"today?"]; + + put(expected.clone()); + assert!(clone() == Some(expected.clone())); + assert!(take() == Some(expected.clone())); + assert!(take() == None); + + do (|| { + }).finally { + // Restore the actual global state. + match saved_value { + Some(ref args) => put(args.clone()), + None => () + } + } + } +} diff --git a/src/libstd/rt/borrowck.rs b/src/libstd/rt/borrowck.rs new file mode 100644 index 0000000000000..60df2d5c11ba2 --- /dev/null +++ b/src/libstd/rt/borrowck.rs @@ -0,0 +1,283 @@ +// 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. + +use cast::transmute; +use libc::{c_char, c_void, size_t, STDERR_FILENO}; +use io; +use io::{Writer, WriterUtil}; +use managed::raw::BoxRepr; +use option::{Option, None, Some}; +use uint; +use str; +use str::OwnedStr; +use sys; +use vec::ImmutableVector; + +#[allow(non_camel_case_types)] +type rust_task = c_void; + +pub static FROZEN_BIT: uint = 1 << (uint::bits - 1); +pub static MUT_BIT: uint = 1 << (uint::bits - 2); +static ALL_BITS: uint = FROZEN_BIT | MUT_BIT; + +#[deriving(Eq)] +struct BorrowRecord { + box: *mut BoxRepr, + file: *c_char, + line: size_t +} + +fn try_take_task_borrow_list() -> Option<~[BorrowRecord]> { + unsafe { + let cur_task: *rust_task = rust_try_get_task(); + if cur_task.is_not_null() { + let ptr = rust_take_task_borrow_list(cur_task); + if ptr.is_null() { + None + } else { + let v: ~[BorrowRecord] = transmute(ptr); + Some(v) + } + } else { + None + } + } +} + +fn swap_task_borrow_list(f: &fn(~[BorrowRecord]) -> ~[BorrowRecord]) { + unsafe { + let cur_task: *rust_task = rust_try_get_task(); + if cur_task.is_not_null() { + let mut borrow_list: ~[BorrowRecord] = { + let ptr = rust_take_task_borrow_list(cur_task); + if ptr.is_null() { ~[] } else { transmute(ptr) } + }; + borrow_list = f(borrow_list); + rust_set_task_borrow_list(cur_task, transmute(borrow_list)); + } + } +} + +pub unsafe fn clear_task_borrow_list() { + // pub because it is used by the box annihilator. + let _ = try_take_task_borrow_list(); +} + +unsafe fn fail_borrowed(box: *mut BoxRepr, file: *c_char, line: size_t) { + debug_borrow("fail_borrowed: ", box, 0, 0, file, line); + + match try_take_task_borrow_list() { + None => { // not recording borrows + let msg = "borrowed"; + do str::as_buf(msg) |msg_p, _| { + sys::begin_unwind_(msg_p as *c_char, file, line); + } + } + Some(borrow_list) => { // recording borrows + let mut msg = ~"borrowed"; + let mut sep = " at "; + for borrow_list.rev_iter().advance |entry| { + if entry.box == box { + msg.push_str(sep); + let filename = str::raw::from_c_str(entry.file); + msg.push_str(filename); + msg.push_str(fmt!(":%u", entry.line as uint)); + sep = " and at "; + } + } + do str::as_buf(msg) |msg_p, _| { + sys::begin_unwind_(msg_p as *c_char, file, line) + } + } + } +} + +/// Because this code is so perf. sensitive, use a static constant so that +/// debug printouts are compiled out most of the time. +static ENABLE_DEBUG: bool = false; + +#[inline] +unsafe fn debug_borrow(tag: &'static str, + p: *const T, + old_bits: uint, + new_bits: uint, + filename: *c_char, + line: size_t) { + //! A useful debugging function that prints a pointer + tag + newline + //! without allocating memory. + + if ENABLE_DEBUG && ::rt::env::get().debug_borrow { + debug_borrow_slow(tag, p, old_bits, new_bits, filename, line); + } + + unsafe fn debug_borrow_slow(tag: &'static str, + p: *const T, + old_bits: uint, + new_bits: uint, + filename: *c_char, + line: size_t) { + let dbg = STDERR_FILENO as io::fd_t; + dbg.write_str(tag); + dbg.write_hex(p as uint); + dbg.write_str(" "); + dbg.write_hex(old_bits); + dbg.write_str(" "); + dbg.write_hex(new_bits); + dbg.write_str(" "); + dbg.write_cstr(filename); + dbg.write_str(":"); + dbg.write_hex(line as uint); + dbg.write_str("\n"); + } +} + +trait DebugPrints { + fn write_hex(&self, val: uint); + unsafe fn write_cstr(&self, str: *c_char); +} + +impl DebugPrints for io::fd_t { + fn write_hex(&self, mut i: uint) { + let letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', + '9', 'a', 'b', 'c', 'd', 'e', 'f']; + static UINT_NIBBLES: uint = ::uint::bytes << 1; + let mut buffer = [0_u8, ..UINT_NIBBLES+1]; + let mut c = UINT_NIBBLES; + while c > 0 { + c -= 1; + buffer[c] = letters[i & 0xF] as u8; + i >>= 4; + } + self.write(buffer.slice(0, UINT_NIBBLES)); + } + + unsafe fn write_cstr(&self, p: *c_char) { + use libc::strlen; + use vec; + + let len = strlen(p); + let p: *u8 = transmute(p); + do vec::raw::buf_as_slice(p, len as uint) |s| { + self.write(s); + } + } +} + +#[inline] +pub unsafe fn borrow_as_imm(a: *u8, file: *c_char, line: size_t) -> uint { + let a: *mut BoxRepr = transmute(a); + let old_ref_count = (*a).header.ref_count; + let new_ref_count = old_ref_count | FROZEN_BIT; + + debug_borrow("borrow_as_imm:", a, old_ref_count, new_ref_count, file, line); + + if (old_ref_count & MUT_BIT) != 0 { + fail_borrowed(a, file, line); + } + + (*a).header.ref_count = new_ref_count; + + old_ref_count +} + +#[inline] +pub unsafe fn borrow_as_mut(a: *u8, file: *c_char, line: size_t) -> uint { + let a: *mut BoxRepr = transmute(a); + let old_ref_count = (*a).header.ref_count; + let new_ref_count = old_ref_count | MUT_BIT | FROZEN_BIT; + + debug_borrow("borrow_as_mut:", a, old_ref_count, new_ref_count, file, line); + + if (old_ref_count & (MUT_BIT|FROZEN_BIT)) != 0 { + fail_borrowed(a, file, line); + } + + (*a).header.ref_count = new_ref_count; + + old_ref_count +} + +pub unsafe fn record_borrow(a: *u8, old_ref_count: uint, + file: *c_char, line: size_t) { + if (old_ref_count & ALL_BITS) == 0 { + // was not borrowed before + let a: *mut BoxRepr = transmute(a); + debug_borrow("record_borrow:", a, old_ref_count, 0, file, line); + do swap_task_borrow_list |borrow_list| { + let mut borrow_list = borrow_list; + borrow_list.push(BorrowRecord {box: a, file: file, line: line}); + borrow_list + } + } +} + +pub unsafe fn unrecord_borrow(a: *u8, old_ref_count: uint, + file: *c_char, line: size_t) { + if (old_ref_count & ALL_BITS) == 0 { + // was not borrowed before, so we should find the record at + // the end of the list + let a: *mut BoxRepr = transmute(a); + debug_borrow("unrecord_borrow:", a, old_ref_count, 0, file, line); + do swap_task_borrow_list |borrow_list| { + let mut borrow_list = borrow_list; + assert!(!borrow_list.is_empty()); + let br = borrow_list.pop(); + if br.box != a || br.file != file || br.line != line { + let err = fmt!("wrong borrow found, br=%?", br); + do str::as_buf(err) |msg_p, _| { + sys::begin_unwind_(msg_p as *c_char, file, line) + } + } + borrow_list + } + } +} + +#[inline] +pub unsafe fn return_to_mut(a: *u8, orig_ref_count: uint, + file: *c_char, line: size_t) { + // Sometimes the box is null, if it is conditionally frozen. + // See e.g. #4904. + if !a.is_null() { + let a: *mut BoxRepr = transmute(a); + let old_ref_count = (*a).header.ref_count; + let new_ref_count = + (old_ref_count & !ALL_BITS) | (orig_ref_count & ALL_BITS); + + debug_borrow("return_to_mut:", + a, old_ref_count, new_ref_count, file, line); + + (*a).header.ref_count = new_ref_count; + } +} + +#[inline] +pub unsafe fn check_not_borrowed(a: *u8, + file: *c_char, + line: size_t) { + let a: *mut BoxRepr = transmute(a); + let ref_count = (*a).header.ref_count; + debug_borrow("check_not_borrowed:", a, ref_count, 0, file, line); + if (ref_count & FROZEN_BIT) != 0 { + fail_borrowed(a, file, line); + } +} + + +extern { + #[rust_stack] + pub fn rust_take_task_borrow_list(task: *rust_task) -> *c_void; + + #[rust_stack] + pub fn rust_set_task_borrow_list(task: *rust_task, map: *c_void); + + #[rust_stack] + pub fn rust_try_get_task() -> *rust_task; +} diff --git a/src/libstd/rt/comm.rs b/src/libstd/rt/comm.rs index 72907f40a0744..fba6171129762 100644 --- a/src/libstd/rt/comm.rs +++ b/src/libstd/rt/comm.rs @@ -19,13 +19,16 @@ use option::*; use cast; use util; use ops::Drop; +use rt::task::Task; use kinds::Send; -use rt::sched::{Scheduler, Coroutine}; +use rt::sched::Scheduler; use rt::local::Local; -use unstable::intrinsics::{atomic_xchg, atomic_load}; +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. /// @@ -34,14 +37,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 +73,7 @@ pub struct PortOneHack { pub fn oneshot() -> (PortOne, ChanOne) { let packet: ~Packet = ~Packet { - state: STATE_BOTH, + state: AtomicUint::new(STATE_BOTH), payload: None }; @@ -114,20 +117,30 @@ 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 + 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; } task_as_state => { // Port is blocked. Wake it up. - let recvr: ~Coroutine = cast::transmute(task_as_state); - let sched = Local::take::(); + let recvr: ~Task = cast::transmute(task_as_state); + let mut sched = Local::take::(); + rtdebug!("rendezvous send"); + sched.metrics.rendezvous_sends += 1; sched.schedule_task(recvr); } } @@ -158,23 +171,30 @@ 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 // 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. + 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. - let task: ~Coroutine = cast::transmute(task_as_state); - let sched = Local::take::(); - sched.resume_task_immediately(task); + // 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: ~Task = cast::transmute(task_as_state); + sched.enqueue_task(task); } _ => util::unreachable() } @@ -210,7 +230,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(), @@ -227,7 +247,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. @@ -238,7 +258,7 @@ impl Drop for ChanOneHack { task_as_state => { // The port is blocked waiting for a message we will never send. Wake it. assert!((*this.packet()).payload.is_none()); - let recvr: ~Coroutine = cast::transmute(task_as_state); + let recvr: ~Task = cast::transmute(task_as_state); let sched = Local::take::(); sched.schedule_task(recvr); } @@ -254,7 +274,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. @@ -295,16 +315,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) { @@ -357,6 +380,136 @@ 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() + } + } +} + +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() + } + } +} + +// 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::*; @@ -402,6 +555,8 @@ mod test { { let _c = chan; } port.recv(); }; + // What is our res? + rtdebug!("res is: %?", res.is_err()); assert!(res.is_err()); } } @@ -584,7 +739,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); @@ -594,18 +749,18 @@ mod test { if i == 10 { return } let chan_cell = Cell::new(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::new(port); - let _thread = do spawntask_thread { + do spawntask_random { let port = port_cell.take(); assert!(port.recv() == ~i); recv(port, i + 1); @@ -614,4 +769,144 @@ 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() } + } + } + + #[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(); + } + } + } + + #[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); + } + } + + #[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(); + } + } + } + } diff --git a/src/libstd/rt/global_heap.rs b/src/libstd/rt/global_heap.rs index 54deb8924f5c7..53634f0cf86e4 100644 --- a/src/libstd/rt/global_heap.rs +++ b/src/libstd/rt/global_heap.rs @@ -8,7 +8,7 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use libc::{c_char, c_void, size_t, uintptr_t, free, malloc, realloc}; +use libc::{c_void, c_char, size_t, uintptr_t, free, malloc, realloc}; use managed::raw::{BoxHeaderRepr, BoxRepr}; use unstable::intrinsics::TyDesc; use sys::size_of; @@ -95,6 +95,11 @@ pub unsafe fn vector_exchange_malloc(align: u32, size: uintptr_t) -> *c_char { // FIXME: #7496 #[cfg(not(test))] #[lang="closure_exchange_malloc"] +#[inline] +pub unsafe fn closure_exchange_malloc_(td: *c_char, size: uintptr_t) -> *c_char { + closure_exchange_malloc(td, size) +} + #[inline] pub unsafe fn closure_exchange_malloc(td: *c_char, size: uintptr_t) -> *c_char { let td = td as *TyDesc; @@ -115,6 +120,11 @@ pub unsafe fn closure_exchange_malloc(td: *c_char, size: uintptr_t) -> *c_char { // inside a landing pad may corrupt the state of the exception handler. #[cfg(not(test))] #[lang="exchange_free"] +#[inline] +pub unsafe fn exchange_free_(ptr: *c_char) { + exchange_free(ptr) +} + #[inline] pub unsafe fn exchange_free(ptr: *c_char) { free(ptr as *c_void); diff --git a/src/libstd/rt/io/net/ip.rs b/src/libstd/rt/io/net/ip.rs index df1dfe4d38ad1..3a93fd705436e 100644 --- a/src/libstd/rt/io/net/ip.rs +++ b/src/libstd/rt/io/net/ip.rs @@ -8,7 +8,10 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +type Port = u16; + +#[deriving(Eq, TotalEq)] pub enum IpAddr { - Ipv4(u8, u8, u8, u8, u16), - Ipv6 + Ipv4(u8, u8, u8, u8, Port), + Ipv6(u16, u16, u16, u16, u16, u16, u16, u16, Port) } diff --git a/src/libstd/rt/io/net/tcp.rs b/src/libstd/rt/io/net/tcp.rs index 3607f781da3ff..2425c909bf3d8 100644 --- a/src/libstd/rt/io/net/tcp.rs +++ b/src/libstd/rt/io/net/tcp.rs @@ -18,15 +18,11 @@ use rt::rtio::{IoFactory, IoFactoryObject, RtioTcpStream, RtioTcpStreamObject}; use rt::local::Local; -pub struct TcpStream { - rtstream: ~RtioTcpStreamObject -} +pub struct TcpStream(~RtioTcpStreamObject); impl TcpStream { fn new(s: ~RtioTcpStreamObject) -> TcpStream { - TcpStream { - rtstream: s - } + TcpStream(s) } pub fn connect(addr: IpAddr) -> Option { @@ -38,13 +34,11 @@ impl TcpStream { }; match stream { - Ok(s) => { - Some(TcpStream::new(s)) - } + Ok(s) => Some(TcpStream::new(s)), Err(ioerr) => { rtdebug!("failed to connect: %?", ioerr); io_error::cond.raise(ioerr); - return None; + None } } } @@ -52,8 +46,7 @@ impl TcpStream { impl Reader for TcpStream { fn read(&mut self, buf: &mut [u8]) -> Option { - let bytes_read = self.rtstream.read(buf); - match bytes_read { + match (**self).read(buf) { Ok(read) => Some(read), Err(ioerr) => { // EOF is indicated by returning None @@ -70,8 +63,7 @@ impl Reader for TcpStream { impl Writer for TcpStream { fn write(&mut self, buf: &[u8]) { - let res = self.rtstream.write(buf); - match res { + match (**self).write(buf) { Ok(_) => (), Err(ioerr) => { io_error::cond.raise(ioerr); @@ -82,9 +74,7 @@ impl Writer for TcpStream { fn flush(&mut self) { fail!() } } -pub struct TcpListener { - rtlistener: ~RtioTcpListenerObject, -} +pub struct TcpListener(~RtioTcpListenerObject); impl TcpListener { pub fn bind(addr: IpAddr) -> Option { @@ -93,11 +83,7 @@ impl TcpListener { (*io).tcp_bind(addr) }; match listener { - Ok(l) => { - Some(TcpListener { - rtlistener: l - }) - } + Ok(l) => Some(TcpListener(l)), Err(ioerr) => { io_error::cond.raise(ioerr); return None; @@ -108,8 +94,7 @@ impl TcpListener { impl Listener for TcpListener { fn accept(&mut self) -> Option { - let rtstream = self.rtlistener.accept(); - match rtstream { + match (**self).accept() { Ok(s) => { Some(TcpStream::new(s)) } @@ -163,7 +148,7 @@ mod test { } #[test] - fn smoke_test() { + fn smoke_test_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); @@ -183,7 +168,27 @@ mod test { } #[test] - fn read_eof() { + fn smoke_test_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + let mut stream = listener.accept(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + } + + do spawntask_immediately { + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + + #[test] + fn read_eof_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); @@ -203,7 +208,27 @@ mod test { } #[test] - fn read_eof_twice() { + fn read_eof_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + let mut stream = listener.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + } + + do spawntask_immediately { + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn read_eof_twice_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); @@ -225,7 +250,29 @@ mod test { } #[test] - fn write_close() { + fn read_eof_twice_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + let mut stream = listener.accept(); + let mut buf = [0]; + let nread = stream.read(buf); + assert!(nread.is_none()); + let nread = stream.read(buf); + assert!(nread.is_none()); + } + + do spawntask_immediately { + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn write_close_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); @@ -254,7 +301,36 @@ mod test { } #[test] - fn multiple_connect_serial() { + fn write_close_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + let mut stream = listener.accept(); + let buf = [0]; + loop { + let mut stop = false; + do io_error::cond.trap(|e| { + // NB: ECONNRESET on linux, EPIPE on mac + assert!(e.kind == ConnectionReset || e.kind == BrokenPipe); + stop = true; + }).in { + stream.write(buf); + } + if stop { break } + } + } + + do spawntask_immediately { + let _stream = TcpStream::connect(addr); + // Close + } + } + } + + #[test] + fn multiple_connect_serial_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); let max = 10; @@ -279,7 +355,32 @@ mod test { } #[test] - fn multiple_connect_interleaved_greedy_schedule() { + fn multiple_connect_serial_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + let max = 10; + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + for max.times { + let mut stream = listener.accept(); + let mut buf = [0]; + stream.read(buf); + assert_eq!(buf[0], 99); + } + } + + do spawntask_immediately { + for max.times { + let mut stream = TcpStream::connect(addr); + stream.write([99]); + } + } + } + } + + #[test] + fn multiple_connect_interleaved_greedy_schedule_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); static MAX: int = 10; @@ -318,7 +419,46 @@ mod test { } #[test] - fn multiple_connect_interleaved_lazy_schedule() { + fn multiple_connect_interleaved_greedy_schedule_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + static MAX: int = 10; + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + for int::range(0, MAX) |i| { + let stream = Cell::new(listener.accept()); + rtdebug!("accepted"); + // Start another task to handle the connection + do spawntask_immediately { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == i as u8); + rtdebug!("read"); + } + } + } + + connect(0, addr); + + fn connect(i: int, addr: IpAddr) { + if i == MAX { return } + + do spawntask_immediately { + rtdebug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + rtdebug!("writing"); + stream.write([i as u8]); + } + } + } + } + + #[test] + fn multiple_connect_interleaved_lazy_schedule_ip4() { do run_in_newsched_task { let addr = next_test_ip4(); static MAX: int = 10; @@ -355,5 +495,43 @@ mod test { } } } + #[test] + fn multiple_connect_interleaved_lazy_schedule_ip6() { + do run_in_newsched_task { + let addr = next_test_ip6(); + static MAX: int = 10; + + do spawntask_immediately { + let mut listener = TcpListener::bind(addr); + for int::range(0, MAX) |_| { + let stream = Cell::new(listener.accept()); + rtdebug!("accepted"); + // Start another task to handle the connection + do spawntask_later { + let mut stream = stream.take(); + let mut buf = [0]; + stream.read(buf); + assert!(buf[0] == 99); + rtdebug!("read"); + } + } + } + + connect(0, addr); + + fn connect(i: int, addr: IpAddr) { + if i == MAX { return } + + do spawntask_later { + rtdebug!("connecting"); + let mut stream = TcpStream::connect(addr); + // Connect again before writing + connect(i + 1, addr); + rtdebug!("writing"); + stream.write([99]); + } + } + } + } } diff --git a/src/libstd/rt/io/net/udp.rs b/src/libstd/rt/io/net/udp.rs index bb5457e334dda..f3b5278357392 100644 --- a/src/libstd/rt/io/net/udp.rs +++ b/src/libstd/rt/io/net/udp.rs @@ -8,38 +8,247 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -use prelude::*; -use super::super::*; -use super::ip::IpAddr; +use option::{Option, Some, None}; +use result::{Ok, Err}; +use rt::io::net::ip::IpAddr; +use rt::io::{Reader, Writer}; +use rt::io::{io_error, read_error, EndOfFile}; +use rt::rtio::{RtioUdpSocketObject, RtioUdpSocket, IoFactory, IoFactoryObject}; +use rt::local::Local; -pub struct UdpStream; +pub struct UdpSocket(~RtioUdpSocketObject); -impl UdpStream { - pub fn connect(_addr: IpAddr) -> Option { - fail!() +impl UdpSocket { + pub fn bind(addr: IpAddr) -> Option { + let socket = unsafe { (*Local::unsafe_borrow::()).udp_bind(addr) }; + match socket { + Ok(s) => Some(UdpSocket(s)), + Err(ioerr) => { + io_error::cond.raise(ioerr); + None + } + } + } + + pub fn recvfrom(&self, buf: &mut [u8]) -> Option<(uint, IpAddr)> { + match (**self).recvfrom(buf) { + Ok((nread, src)) => Some((nread, src)), + Err(ioerr) => { + // EOF is indicated by returning None + if ioerr.kind != EndOfFile { + read_error::cond.raise(ioerr); + } + None + } + } + } + + pub fn sendto(&self, buf: &[u8], dst: IpAddr) { + match (**self).sendto(buf, dst) { + Ok(_) => (), + Err(ioerr) => io_error::cond.raise(ioerr), + } + } + + pub fn connect(self, other: IpAddr) -> UdpStream { + UdpStream { socket: self, connectedTo: other } } } +pub struct UdpStream { + socket: UdpSocket, + connectedTo: IpAddr +} + +impl UdpStream { + pub fn as_socket(&self, f: &fn(&UdpSocket) -> T) -> T { f(&self.socket) } + + pub fn disconnect(self) -> UdpSocket { self.socket } +} + impl Reader for UdpStream { - fn read(&mut self, _buf: &mut [u8]) -> Option { fail!() } + fn read(&mut self, buf: &mut [u8]) -> Option { + do self.as_socket |sock| { + match sock.recvfrom(buf) { + Some((_nread, src)) if src != self.connectedTo => Some(0), + Some((nread, _src)) => Some(nread), + None => None, + } + } + } fn eof(&mut self) -> bool { fail!() } } impl Writer for UdpStream { - fn write(&mut self, _buf: &[u8]) { fail!() } + fn write(&mut self, buf: &[u8]) { + do self.as_socket |sock| { + sock.sendto(buf, self.connectedTo); + } + } fn flush(&mut self) { fail!() } } -pub struct UdpListener; +#[cfg(test)] +mod test { + use super::*; + use rt::test::*; + use rt::io::net::ip::Ipv4; + use rt::io::*; + use option::{Some, None}; -impl UdpListener { - pub fn bind(_addr: IpAddr) -> Option { - fail!() + #[test] #[ignore] + fn bind_error() { + do run_in_newsched_task { + let mut called = false; + do io_error::cond.trap(|e| { + assert!(e.kind == PermissionDenied); + called = true; + }).in { + let addr = Ipv4(0, 0, 0, 0, 1); + let socket = UdpSocket::bind(addr); + assert!(socket.is_none()); + } + assert!(called); + } } -} -impl Listener for UdpListener { - fn accept(&mut self) -> Option { fail!() } + #[test] + fn socket_smoke_test_ip4() { + do run_in_newsched_task { + let server_ip = next_test_ip4(); + let client_ip = next_test_ip4(); + + do spawntask_immediately { + match UdpSocket::bind(server_ip) { + Some(server) => { + let mut buf = [0]; + match server.recvfrom(buf) { + Some((nread, src)) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask_immediately { + match UdpSocket::bind(client_ip) { + Some(client) => client.sendto([99], server_ip), + None => fail!() + } + } + } + } + + #[test] + fn socket_smoke_test_ip6() { + do run_in_newsched_task { + let server_ip = next_test_ip6(); + let client_ip = next_test_ip6(); + + do spawntask_immediately { + match UdpSocket::bind(server_ip) { + Some(server) => { + let mut buf = [0]; + match server.recvfrom(buf) { + Some((nread, src)) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + assert_eq!(src, client_ip); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask_immediately { + match UdpSocket::bind(client_ip) { + Some(client) => client.sendto([99], server_ip), + None => fail!() + } + } + } + } + + #[test] + fn stream_smoke_test_ip4() { + do run_in_newsched_task { + let server_ip = next_test_ip4(); + let client_ip = next_test_ip4(); + + do spawntask_immediately { + match UdpSocket::bind(server_ip) { + Some(server) => { + let server = ~server; + let mut stream = server.connect(client_ip); + let mut buf = [0]; + match stream.read(buf) { + Some(nread) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask_immediately { + match UdpSocket::bind(client_ip) { + Some(client) => { + let client = ~client; + let mut stream = client.connect(server_ip); + stream.write([99]); + } + None => fail!() + } + } + } + } + + #[test] + fn stream_smoke_test_ip6() { + do run_in_newsched_task { + let server_ip = next_test_ip6(); + let client_ip = next_test_ip6(); + + do spawntask_immediately { + match UdpSocket::bind(server_ip) { + Some(server) => { + let server = ~server; + let mut stream = server.connect(client_ip); + let mut buf = [0]; + match stream.read(buf) { + Some(nread) => { + assert_eq!(nread, 1); + assert_eq!(buf[0], 99); + } + None => fail!() + } + } + None => fail!() + } + } + + do spawntask_immediately { + match UdpSocket::bind(client_ip) { + Some(client) => { + let client = ~client; + let mut stream = client.connect(server_ip); + stream.write([99]); + } + None => fail!() + } + } + } + } } diff --git a/src/libstd/rt/join_latch.rs b/src/libstd/rt/join_latch.rs new file mode 100644 index 0000000000000..8073c4a75b88b --- /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 drop(&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 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::new(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::new(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::new(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::new(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::new(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::new(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::new(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::new(child_latch); + + do spawntask_immediately { + let mut latch = child_latch.take(); + let child_latch = latch.new_child(); + let child_latch = Cell::new(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::new(child_latch); + + do spawntask_later { + let mut latch = child_latch.take(); + let child_latch = latch.new_child(); + let child_latch = Cell::new(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.iter().advance |&order| { + let child_latch = latch.new_child(); + let child_latch = Cell::new(child_latch); + let (child_orders, remaining) = split_orders(remaining_orders); + rtdebug!("(child_orders, remaining): %?", (&child_orders, &remaining)); + remaining_orders = remaining; + let child_orders = Cell::new(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.iter().advance |order| { + let suborders = copy order.orders; + let child_latch = Cell::new(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/local.rs b/src/libstd/rt/local.rs index 313123c38b58d..b47bbf3edf0bb 100644 --- a/src/libstd/rt/local.rs +++ b/src/libstd/rt/local.rs @@ -13,12 +13,13 @@ use rt::sched::Scheduler; use rt::task::Task; use rt::local_ptr; use rt::rtio::{EventLoop, IoFactoryObject}; +//use borrow::to_uint; 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,23 +28,40 @@ 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| { +// rtdebug!("successfully unsafe borrowed sched pointer"); + let result = f(sched); + *res_ptr = Some(result); + } + } + match res { + Some(r) => { r } + 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 borrow(f: &fn(&mut Task)) { - do Local::borrow:: |sched| { + 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| { +// rtdebug!("sched about to grab current_task"); match sched.current_task { Some(~ref mut task) => { - f(&mut *task.task) +// rtdebug!("current task pointer: %x", to_uint(task)); +// rtdebug!("current task heap pointer: %x", to_uint(&task.heap)); + f(task) } None => { - abort!("no scheduler") + rtabort!("no scheduler") } } } @@ -51,12 +69,12 @@ impl Local for Task { unsafe fn unsafe_borrow() -> *mut Task { match (*Local::unsafe_borrow::()).current_task { Some(~ref mut task) => { - let s: *mut Task = &mut *task.task; + let s: *mut Task = &mut *task; return s; } None => { // Don't fail. Infinite recursion - abort!("no scheduler") + rtabort!("no scheduler") } } } @@ -71,48 +89,69 @@ 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)) { 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)] mod test { + use unstable::run_in_bare_thread; + 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(); - Local::put(scheduler); - let _scheduler: ~Scheduler = Local::take(); + do run_in_bare_thread { + 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(); - Local::put(scheduler); - let _scheduler: ~Scheduler = Local::take(); - let scheduler = ~UvEventLoop::new_scheduler(); - Local::put(scheduler); - let _scheduler: ~Scheduler = Local::take(); + do run_in_bare_thread { + let scheduler = ~new_test_uv_sched(); + Local::put(scheduler); + let _scheduler: ~Scheduler = Local::take(); + let scheduler = ~new_test_uv_sched(); + Local::put(scheduler); + let _scheduler: ~Scheduler = Local::take(); + } } #[test] fn borrow_smoke_test() { - let scheduler = ~UvEventLoop::new_scheduler(); - Local::put(scheduler); - unsafe { - let _scheduler: *mut Scheduler = Local::unsafe_borrow(); + do run_in_bare_thread { + let scheduler = ~new_test_uv_sched(); + Local::put(scheduler); + unsafe { + let _scheduler: *mut Scheduler = Local::unsafe_borrow(); + } + let _scheduler: ~Scheduler = Local::take(); } - let _scheduler: ~Scheduler = Local::take(); } + + #[test] + fn borrow_with_return() { + do run_in_bare_thread { + 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/local_heap.rs b/src/libstd/rt/local_heap.rs index c5c22f4515939..c909bdb62850a 100644 --- a/src/libstd/rt/local_heap.rs +++ b/src/libstd/rt/local_heap.rs @@ -10,11 +10,24 @@ //! The local, garbage collected heap +use libc; use libc::{c_void, uintptr_t, size_t}; use ops::Drop; +use repr::BoxRepr; +use rt; +use rt::OldTaskContext; +use rt::local::Local; +use rt::task::Task; type MemoryRegion = c_void; -type BoxedRegion = c_void; + +struct Env { priv opaque: () } + +struct BoxedRegion { + env: *Env, + backing_region: *MemoryRegion, + live_allocs: *BoxRepr +} pub type OpaqueBox = c_void; pub type TypeDesc = c_void; @@ -49,6 +62,12 @@ impl LocalHeap { } } + pub fn realloc(&mut self, ptr: *OpaqueBox, size: uint) -> *OpaqueBox { + unsafe { + return rust_boxed_region_realloc(self.boxed_region, ptr, size as size_t); + } + } + pub fn free(&mut self, box: *OpaqueBox) { unsafe { return rust_boxed_region_free(self.boxed_region, box); @@ -65,6 +84,40 @@ impl Drop for LocalHeap { } } +// A little compatibility function +pub unsafe fn local_free(ptr: *libc::c_char) { + match rt::context() { + OldTaskContext => { + rust_upcall_free_noswitch(ptr); + + extern { + #[fast_ffi] + unsafe fn rust_upcall_free_noswitch(ptr: *libc::c_char); + } + } + _ => { + do Local::borrow:: |task| { + task.heap.free(ptr as *libc::c_void); + } + } + } +} + +pub fn live_allocs() -> *BoxRepr { + let region = match rt::context() { + OldTaskContext => { + unsafe { rust_current_boxed_region() } + } + _ => { + do Local::borrow:: |task| { + task.heap.boxed_region + } + } + }; + + return unsafe { (*region).live_allocs }; +} + extern { fn rust_new_memory_region(synchronized: uintptr_t, detailed_leaks: uintptr_t, @@ -76,5 +129,9 @@ extern { fn rust_boxed_region_malloc(region: *BoxedRegion, td: *TypeDesc, size: size_t) -> *OpaqueBox; + fn rust_boxed_region_realloc(region: *BoxedRegion, + ptr: *OpaqueBox, + size: size_t) -> *OpaqueBox; fn rust_boxed_region_free(region: *BoxedRegion, box: *OpaqueBox); + fn rust_current_boxed_region() -> *BoxedRegion; } diff --git a/src/libstd/rt/local_ptr.rs b/src/libstd/rt/local_ptr.rs index 0db903f81eec7..cd7c5daa444d7 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/logging.rs b/src/libstd/rt/logging.rs index a0d0539768912..84186180aa650 100644 --- a/src/libstd/rt/logging.rs +++ b/src/libstd/rt/logging.rs @@ -9,6 +9,7 @@ // except according to those terms. use either::*; +use libc; pub trait Logger { fn log(&mut self, msg: Either<~str, &'static str>); @@ -20,6 +21,10 @@ impl Logger for StdErrLogger { fn log(&mut self, msg: Either<~str, &'static str>) { use io::{Writer, WriterUtil}; + if !should_log_console() { + return; + } + let s: &str = match msg { Left(ref s) => { let s: &str = *s; @@ -44,7 +49,6 @@ pub fn init(crate_map: *u8) { use str; use ptr; use option::{Some, None}; - use libc::c_char; let log_spec = os::getenv("RUST_LOG"); match log_spec { @@ -61,8 +65,16 @@ pub fn init(crate_map: *u8) { } } } +} - extern { - fn rust_update_log_settings(crate_map: *u8, settings: *c_char); - } +pub fn console_on() { unsafe { rust_log_console_on() } } +pub fn console_off() { unsafe { rust_log_console_off() } } +fn should_log_console() -> bool { unsafe { rust_should_log_console() != 0 } } + +extern { + fn rust_update_log_settings(crate_map: *u8, settings: *libc::c_char); + fn rust_log_console_on(); + fn rust_log_console_off(); + fn rust_should_log_console() -> libc::uintptr_t; } + diff --git a/src/libstd/rt/message_queue.rs b/src/libstd/rt/message_queue.rs index d561e81d03271..6ef07577415d3 100644 --- a/src/libstd/rt/message_queue.rs +++ b/src/libstd/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::Send; use vec::OwnedVector; diff --git a/src/libstd/rt/metrics.rs b/src/libstd/rt/metrics.rs new file mode 100644 index 0000000000000..b0c0fa5d70862 --- /dev/null +++ b/src/libstd/rt/metrics.rs @@ -0,0 +1,98 @@ +// 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, + // JoinLatch releases that create tombstones + release_tombstone: uint, + // JoinLatch releases that do not create tombstones + release_no_tombstone: 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, + release_tombstone: 0, + release_no_tombstone: 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\ + release_tombstone: %u\n\ + release_no_tombstone: %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, + 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 f9f433b9416b4..51f4737ef85fb 100644 --- a/src/libstd/rt/mod.rs +++ b/src/libstd/rt/mod.rs @@ -55,8 +55,27 @@ Several modules in `core` are clients of `rt`: */ #[doc(hidden)]; - +#[deny(unused_imports)]; +#[deny(unused_mut)]; +#[deny(unused_variable)]; +#[deny(unused_unsafe)]; + +use cell::Cell; +use clone::Clone; +use container::Container; +use iter::Times; +use iterator::IteratorUtil; +use option::Some; use ptr::RawPtr; +use rt::sched::{Scheduler, 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 unstable::atomics::{AtomicInt, SeqCst}; +use unstable::sync::UnsafeAtomicRcBox; +use vec::{OwnedVector, MutableVector}; /// The global (exchange) heap. pub mod global_heap; @@ -88,6 +107,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; @@ -101,7 +123,7 @@ mod thread; pub mod env; /// The local, managed heap -mod local_heap; +pub mod local_heap; /// The Logger trait and implementations pub mod logging; @@ -127,6 +149,20 @@ pub mod local_ptr; /// Bindings to pthread/windows thread-local storage. pub mod thread_local_storage; +/// For waiting on child tasks. +pub mod join_latch; + +pub mod metrics; + +// FIXME #5248 shouldn't be pub +/// Just stuff +pub mod util; + +// Global command line argument storage +pub mod args; + +// Support for dynamic borrowck +pub mod borrowck; /// Set up a default runtime configuration, given compiler-supplied arguments. /// @@ -142,27 +178,128 @@ pub mod thread_local_storage; /// # Return value /// /// 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 { +pub fn start(argc: int, argv: **u8, crate_map: *u8, main: ~fn()) -> int { - use self::sched::{Scheduler, Coroutine}; - use self::uv::uvio::UvEventLoop; + init(argc, argv, crate_map); + let exit_code = run(main); + cleanup(); - init(crate_map); + return exit_code; +} - let loop_ = ~UvEventLoop::new(); - let mut sched = ~Scheduler::new(loop_); - let main_task = ~Coroutine::new(&mut sched.stack_pool, main); +/// One-time runtime initialization. +/// +/// Initializes global state, including frobbing +/// the crate's logging flags, registering GC +/// metadata, and storing the process arguments. +pub fn init(argc: int, argv: **u8, crate_map: *u8) { + // XXX: Derefing these pointers is not safe. + // Need to propagate the unsafety to `start`. + unsafe { + args::init(argc, argv); + logging::init(crate_map); + rust_update_gc_metadata(crate_map); + } - sched.enqueue_task(main_task); - sched.run(); + extern { + fn rust_update_gc_metadata(crate_map: *u8); + } +} - return 0; +/// One-time runtime cleanup. +pub fn cleanup() { + args::cleanup(); } -/// One-time runtime initialization. Currently all this does is set up logging -/// based on the RUST_LOG environment variable. -pub fn init(crate_map: *u8) { - logging::init(crate_map); +/// 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 = util::default_sched_threads(); + + // 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(); + + // 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 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(); + + 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(); + + // 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 on_exit: ~fn(bool) = |exit_success| { + + let mut handles = handles.take(); + for handles.mut_iter().advance |handle| { + handle.send(Shutdown); + } + + unsafe { + let exit_code = if exit_success { + use rt::util; + + // If we're exiting successfully, then return the global + // exit status, which can be set programmatically. + util::get_exit_status() + } else { + DEFAULT_ERROR_CODE + }; + (*exit_code_clone.get()).store(exit_code, SeqCst); + } + }; + + // Create and enqueue the main task. + let main_cell = Cell::new(main); + let mut main_task = ~Task::new_root(&mut scheds[0].stack_pool, + main_cell.take()); + main_task.on_exit = Some(on_exit); + 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); + let thread = do Thread::start { + let sched = sched_cell.take(); + sched.run(); + }; + + threads.push(thread); + } + + // Wait for schedulers + { let _threads = threads; } + + // Return the exit code + unsafe { + (*exit_code.get()).load(SeqCst) + } } /// Possible contexts in which Rust code may be executing. @@ -194,8 +331,8 @@ pub fn context() -> RuntimeContext { return OldTaskContext; } else { if Local::exists::() { - let context = ::cell::Cell::new_empty(); - do Local::borrow:: |sched| { + let context = Cell::new_empty(); + do Local::borrow:: |sched| { if sched.in_task_context() { context.put_back(TaskContext); } else { @@ -217,24 +354,20 @@ pub fn context() -> RuntimeContext { #[test] fn test_context() { use unstable::run_in_bare_thread; - use self::sched::{Scheduler, Coroutine}; - use rt::uv::uvio::UvEventLoop; - use cell::Cell; + use self::sched::{Scheduler}; 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 task = ~do Coroutine::new(&mut sched.stack_pool) { + let mut sched = ~new_test_uv_sched(); + let task = ~do Task::new_root(&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::new(task); - do Local::borrow:: |sched| { - sched.enqueue_task(task.take()); - } + sched.enqueue_task(task); } }; sched.enqueue_task(task); diff --git a/src/libstd/rt/rtio.rs b/src/libstd/rt/rtio.rs index 4b5eda22ff5de..6bf352ee1447f 100644 --- a/src/libstd/rt/rtio.rs +++ b/src/libstd/rt/rtio.rs @@ -18,28 +18,69 @@ 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; +pub type RtioUdpSocketObject = uvio::UvUdpSocket; 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>; + fn udp_bind(&mut self, addr: IpAddr) -> Result<~RtioUdpSocketObject, IoError>; } -pub trait RtioTcpListener { +pub trait RtioTcpListener : RtioSocket { fn accept(&mut self) -> Result<~RtioTcpStreamObject, IoError>; + fn accept_simultaneously(&self); + fn dont_accept_simultaneously(&self); +} + +pub trait RtioTcpStream : RtioSocket { + fn read(&self, buf: &mut [u8]) -> Result; + fn write(&self, buf: &[u8]) -> Result<(), IoError>; + fn peer_name(&self) -> IpAddr; + fn control_congestion(&self); + fn nodelay(&self); + fn keepalive(&self, delay_in_seconds: uint); + fn letdie(&self); +} + +pub trait RtioSocket { + fn socket_name(&self) -> IpAddr; } -pub trait RtioTcpStream { - fn read(&mut self, buf: &mut [u8]) -> Result; - fn write(&mut self, buf: &[u8]) -> Result<(), IoError>; +pub trait RtioUdpSocket : RtioSocket { + fn recvfrom(&self, buf: &mut [u8]) -> Result<(uint, IpAddr), IoError>; + fn sendto(&self, buf: &[u8], dst: IpAddr) -> Result<(), IoError>; + + fn join_multicast(&self, multi: IpAddr); + fn leave_multicast(&self, multi: IpAddr); + + fn loop_multicast_locally(&self); + fn dont_loop_multicast_locally(&self); + + fn multicast_time_to_live(&self, ttl: int); + fn time_to_live(&self, ttl: int); + + fn hear_broadcasts(&self); + fn ignore_broadcasts(&self); } diff --git a/src/libstd/rt/sched.rs b/src/libstd/rt/sched.rs index 929b44f79b5d9..6e9aef7773051 100644 --- a/src/libstd/rt/sched.rs +++ b/src/libstd/rt/sched.rs @@ -11,22 +11,49 @@ use option::*; use sys; use cast::transmute; -use cell::Cell; +use clone::Clone; +use super::sleeper_list::SleeperList; use super::work_queue::WorkQueue; -use super::stack::{StackPool, StackSegment}; -use super::rtio::{EventLoop, EventLoopObject}; +use super::stack::{StackPool}; +use super::rtio::{EventLoop, EventLoopObject, RemoteCallbackObject}; use super::context::Context; -use super::task::Task; +use super::task::{Task, AnySched, Sched}; +use super::message_queue::MessageQueue; use rt::local_ptr; use rt::local::Local; +use rt::rtio::RemoteCallback; +use rt::metrics::SchedMetrics; +use borrow::{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 /// scheduler. +/// +/// XXX: This creates too many callbacks to run_sched_once, resulting +/// in too much allocation and too many events. pub struct Scheduler { - priv work_queue: WorkQueue<~Coroutine>, + /// A queue of available work. Under a work-stealing policy there + /// is one per Scheduler. + priv work_queue: WorkQueue<~Task>, + /// 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, @@ -34,44 +61,68 @@ pub struct Scheduler { /// Always valid when a task is executing, otherwise not priv saved_context: Context, /// The currently executing task - current_task: Option<~Coroutine>, + current_task: Option<~Task>, /// 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, + /// Should this scheduler run any task, or only pinned tasks? + run_anything: bool } -// 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 SchedHandle { + priv remote: ~RemoteCallbackObject, + priv queue: MessageQueue, + sched_id: uint } -impl ClosureConverter for UnsafeTaskReceiver { - fn from_fn(f: &fn(~Coroutine)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } - fn to_fn(self) -> &fn(~Coroutine) { unsafe { transmute(self) } } + +pub enum SchedMessage { + Wake, + Shutdown, + PinnedTask(~Task) } enum CleanupJob { DoNothing, - GiveTask(~Coroutine, UnsafeTaskReceiver) + GiveTask(~Task, UnsafeTaskReceiver) } impl Scheduler { pub fn in_task_context(&self) -> bool { self.current_task.is_some() } - pub fn new(event_loop: ~EventLoopObject) -> Scheduler { + pub fn sched_id(&self) -> uint { to_uint(self) } + + pub fn new(event_loop: ~EventLoopObject, + work_queue: WorkQueue<~Task>, + sleeper_list: SleeperList) + -> Scheduler { + + Scheduler::new_special(event_loop, work_queue, sleeper_list, true) + + } + + pub fn new_special(event_loop: ~EventLoopObject, + work_queue: WorkQueue<~Task>, + sleeper_list: SleeperList, + run_anything: bool) + -> Scheduler { // Lazily initialize the runtime TLS key local_ptr::init_tls_key(); Scheduler { + sleeper_list: sleeper_list, + message_queue: MessageQueue::new(), + sleepy: false, + no_sleep: false, event_loop: event_loop, - work_queue: WorkQueue::new(), + work_queue: work_queue, stack_pool: StackPool::new(), saved_context: Context::empty(), current_task: None, - cleanup_job: None + cleanup_job: None, + metrics: SchedMetrics::new(), + run_anything: run_anything } } @@ -84,6 +135,11 @@ 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; @@ -96,46 +152,261 @@ impl Scheduler { (*event_loop).run(); } + rtdebug!("run taking sched"); let sched = Local::take::(); - assert!(sched.work_queue.is_empty()); + // XXX: Reenable this once we're using a per-scheduler queue. With a shared + // queue this is not true + //assert!(sched.work_queue.is_empty()); + rtdebug!("scheduler metrics: %s\n", { + use to_str::ToStr; + 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. + 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); + Local::put(sched); + return; + } + + // 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 + // to do yet, so let's try again later. + do Local::borrow:: |sched| { + sched.metrics.tasks_resumed_from_queue += 1; + sched.event_loop.callback(Scheduler::run_sched_once); + } + 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"); + do Local::borrow:: |sched| { + 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); + } else { + rtdebug!("not sleeping"); + } + } + } + + pub fn make_handle(&mut self) -> SchedHandle { + let remote = self.event_loop.remote_callback(Scheduler::run_sched_once); + + return SchedHandle { + remote: remote, + 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. - pub fn enqueue_task(&mut self, task: ~Coroutine) { - self.work_queue.push(task); - self.event_loop.callback(resume_task_from_queue); + /// 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. + pub fn enqueue_task(&mut self, task: ~Task) { - fn resume_task_from_queue() { - let scheduler = Local::take::(); - scheduler.resume_task_from_queue(); - } + // 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; + + // We push the task onto our local queue clone. + 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 */) } + }; } // * Scheduler-context operations - pub fn resume_task_from_queue(~self) { + fn interpret_message_queue(~self) -> bool { assert!(!self.in_task_context()); - rtdebug!("looking in work queue for task to schedule"); + rtdebug!("looking for scheduler messages"); let mut this = self; - match this.work_queue.pop() { - Some(task) => { - rtdebug!("resuming task from work queue"); + match this.message_queue.pop() { + Some(PinnedTask(task)) => { + rtdebug!("recv BiasedTask message in sched: %u", + this.sched_id()); + let mut task = task; + task.home = Some(Sched(this.make_handle())); this.resume_task_immediately(task); + return true; + } + + 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 => break + } + } + } + // 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 => { - rtdebug!("no tasks in queue"); Local::put(this); + return false; + } + } + } + + /// Given an input Coroutine sends it back to its home scheduler. + fn send_task_home(task: ~Task) { + let mut task = task; + let mut home = task.home.swap_unwrap(); + match home { + Sched(ref mut home_handle) => { + home_handle.send(PinnedTask(task)); + } + AnySched => { + rtabort!("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; + + // 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.home; + match home { + &Some(Sched(ref home_handle)) + if home_handle.sched_id != this.sched_id() => { + SendHome + } + &Some(AnySched) if this.run_anything => { + ResumeNow + } + &Some(AnySched) => { + Requeue + } + &Some(Sched(_)) => { + ResumeNow + } + &None => { + Homeless + } + } + }; + + match action_id { + SendHome => { + rtdebug!("sending task home"); + Scheduler::send_task_home(task); + Local::put(this); + return false; + } + ResumeNow => { + rtdebug!("resuming now"); + this.resume_task_immediately(task); + return true; + } + Requeue => { + rtdebug!("re-queueing") + this.enqueue_task(task); + Local::put(this); + return false; + } + Homeless => { + rtabort!("task home was None!"); + } + } + } + + None => { + rtdebug!("no tasks in queue"); + Local::put(this); + return false; + } + } + } + // * Task-context operations /// Called by a running task to end execution, after which it will @@ -145,45 +416,51 @@ impl Scheduler { rtdebug!("ending running task"); - do self.deschedule_running_task_and_then |dead_task| { - let dead_task = Cell::new(dead_task); - do Local::borrow:: |sched| { - dead_task.take().recycle(&mut sched.stack_pool); - } + do self.deschedule_running_task_and_then |sched, dead_task| { + let mut dead_task = dead_task; + let coroutine = dead_task.coroutine.swap_unwrap(); + coroutine.recycle(&mut sched.stack_pool); } - abort!("control reached end of task"); + rtabort!("control reached end of task"); } - pub fn schedule_new_task(~self, task: ~Coroutine) { + pub fn schedule_task(~self, task: ~Task) { assert!(self.in_task_context()); - do self.switch_running_tasks_and_then(task) |last_task| { - let last_task = Cell::new(last_task); - do Local::borrow:: |sched| { - sched.enqueue_task(last_task.take()); - } - } - } + // is the task home? + let is_home = task.is_home_no_tls(&self); - pub 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) |last_task| { - let last_task = Cell::new(last_task); - do Local::borrow:: |sched| { - 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| { + sched.enqueue_task(last_task); } + } 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); } } // Core scheduling ops - pub fn resume_task_immediately(~self, task: ~Coroutine) { + pub fn resume_task_immediately(~self, task: ~Task) { let mut this = self; 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); @@ -217,15 +494,21 @@ 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. - pub 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. + pub fn deschedule_running_task_and_then(~self, f: &fn(&mut Scheduler, ~Task)) { let mut this = self; 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(); - let f_fake_region = transmute::<&fn(~Coroutine), &fn(~Coroutine)>(f); + let f_fake_region = transmute::<&fn(&mut Scheduler, ~Task), + &fn(&mut Scheduler, ~Task)>(f); let f_opaque = ClosureConverter::from_fn(f_fake_region); this.enqueue_cleanup_job(GiveTask(blocked_task, f_opaque)); } @@ -247,16 +530,19 @@ 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. - pub fn switch_running_tasks_and_then(~self, - next_task: ~Coroutine, - f: &fn(~Coroutine)) { + pub fn switch_running_tasks_and_then(~self, next_task: ~Task, + f: &fn(&mut Scheduler, ~Task)) { let mut this = self; 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 { transmute::<&fn(~Coroutine), &fn(~Coroutine)>(f) }; + let f_fake_region = unsafe { + transmute::<&fn(&mut Scheduler, ~Task), + &fn(&mut Scheduler, ~Task)>(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); @@ -293,7 +579,7 @@ 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) } } @@ -322,12 +608,22 @@ impl Scheduler { // because borrowck thinks the three patterns are conflicting // borrows unsafe { - let last_task = transmute::, Option<&mut Coroutine>>(last_task); + let last_task = transmute::, Option<&mut Task>>(last_task); let last_task_context = match last_task { - Some(t) => Some(&mut t.saved_context), None => None + Some(t) => { + Some(&mut t.coroutine.get_mut_ref().saved_context) + } + None => { + None + } }; let next_task_context = match self.current_task { - Some(ref mut t) => Some(&mut t.saved_context), None => None + Some(ref mut t) => { + Some(&mut t.coroutine.get_mut_ref().saved_context) + } + None => { + None + } }; // XXX: These transmutes can be removed after snapshot return (transmute(&mut self.saved_context), @@ -337,80 +633,239 @@ 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 +// The cases for the below function. +enum ResumeAction { + SendHome, + Requeue, + ResumeNow, + Homeless } -impl Coroutine { - pub fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { - Coroutine::with_task(stack_pool, ~Task::new(), start) - } - - pub fn with_task(stack_pool: &mut StackPool, - task: ~Task, - start: ~fn()) -> Coroutine { - 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 { - current_stack_segment: stack, - saved_context: initial_context, - task: task - }; - } - - fn build_start_wrapper(start: ~fn()) -> ~fn() { - // XXX: The old code didn't have this extra allocation - let wrapper: ~fn() = || { - // This is the first code to execute after the initial - // context switch to the task. The previous context may - // have asked us to do some cleanup. - unsafe { - let sched = Local::unsafe_borrow::(); - (*sched).run_cleanup_job(); - - 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 sched = Local::take::(); - sched.terminate_current_task(); - }; - return wrapper; +impl SchedHandle { + pub fn send(&mut self, msg: SchedMessage) { + self.queue.push(msg); + self.remote.fire(); } +} - /// Destroy the task and try to reuse its components - pub fn recycle(~self, stack_pool: &mut StackPool) { - match self { - ~Coroutine {current_stack_segment, _} => { - stack_pool.give_segment(current_stack_segment); - } - } - } +// XXX: Some hacks to put a &fn in Scheduler without borrowck +// complaining +type UnsafeTaskReceiver = sys::Closure; +trait ClosureConverter { + fn from_fn(&fn(&mut Scheduler, ~Task)) -> Self; + fn to_fn(self) -> &fn(&mut Scheduler, ~Task); } +impl ClosureConverter for UnsafeTaskReceiver { + fn from_fn(f: &fn(&mut Scheduler, ~Task)) -> UnsafeTaskReceiver { unsafe { transmute(f) } } + fn to_fn(self) -> &fn(&mut Scheduler, ~Task) { unsafe { transmute(self) } } +} + #[cfg(test)] 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; use rt::test::*; use super::*; + use rt::thread::Thread; + use borrow::to_uint; + use rt::task::{Task,Sched}; + + // 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 Task::new_root_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); + } + } + + // 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 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::new(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::new(special_sched.make_handle()); + let special_handle2 = Cell::new(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 Task::new_root_homed(&mut special_sched.stack_pool, + Sched(t1_handle)) || { + let is_home = Task::is_home_using_id(special_id); + rtdebug!("t1 should be home: %b", is_home); + assert!(is_home); + }; + let t1f = Cell::new(t1f); + + let t2f = ~do Task::new_root(&mut normal_sched.stack_pool) { + let on_special = Task::on_special(); + rtdebug!("t2 should not be on special: %b", on_special); + assert!(!on_special); + }; + let t2f = Cell::new(t2f); + + let t3f = ~do Task::new_root(&mut normal_sched.stack_pool) { + // not on special + let on_special = Task::on_special(); + rtdebug!("t3 should not be on special: %b", on_special); + assert!(!on_special); + }; + let t3f = Cell::new(t3f); + + let t4f = ~do Task::new_root_homed(&mut special_sched.stack_pool, + Sched(t4_handle)) { + // is home + let home = Task::is_home_using_id(special_id); + rtdebug!("t4 should be home: %b", home); + assert!(home); + }; + let t4f = Cell::new(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::new(t1); + let t2 = Cell::new(t2); + let t3 = Cell::new(t3); + let t4 = Cell::new(t4); + + // build a main task that runs our four tests + let main_task = ~do Task::new_root(&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 Task::new_root_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::new(normal_sched); + let normal_thread = do Thread::start { + let sched = nsched_cell.take(); + sched.run(); + }; + + let ssched_cell = Cell::new(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; + + } + } + + // 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(); + } + } #[test] fn test_simple_scheduling() { @@ -418,8 +873,8 @@ mod test { let mut task_ran = false; let task_ran_ptr: *mut bool = &mut task_ran; - let mut sched = ~UvEventLoop::new_scheduler(); - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let mut sched = ~new_test_uv_sched(); + let task = ~do Task::new_root(&mut sched.stack_pool) { unsafe { *task_ran_ptr = true; } }; sched.enqueue_task(task); @@ -435,9 +890,9 @@ 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) { + let task = ~do Task::new_root(&mut sched.stack_pool) { unsafe { *task_count_ptr = *task_count_ptr + 1; } }; sched.enqueue_task(task); @@ -453,19 +908,17 @@ mod test { let mut count = 0; let count_ptr: *mut int = &mut count; - let mut sched = ~UvEventLoop::new_scheduler(); - let task1 = ~do Coroutine::new(&mut sched.stack_pool) { + let mut sched = ~new_test_uv_sched(); + let task1 = ~do Task::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 Task::new_root(&mut sched.stack_pool) { 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::new(task1); - do Local::borrow:: |sched| { - sched.enqueue_task(task1.take()); - } + sched.enqueue_task(task1.take()); } unsafe { *count_ptr = *count_ptr + 1; } }; @@ -482,9 +935,9 @@ 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) { + let start_task = ~do Task::new_root(&mut sched.stack_pool) { run_task(count_ptr); }; sched.enqueue_task(start_task); @@ -493,8 +946,8 @@ mod test { assert_eq!(count, MAX); fn run_task(count_ptr: *mut int) { - do Local::borrow:: |sched| { - let task = ~do Coroutine::new(&mut sched.stack_pool) { + do Local::borrow:: |sched| { + let task = ~do Task::new_root(&mut sched.stack_pool) { unsafe { *count_ptr = *count_ptr + 1; if *count_ptr != MAX { @@ -511,16 +964,14 @@ mod test { #[test] fn test_block_task() { do run_in_bare_thread { - let mut sched = ~UvEventLoop::new_scheduler(); - let task = ~do Coroutine::new(&mut sched.stack_pool) { + let mut sched = ~new_test_uv_sched(); + let task = ~do Task::new_root(&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::new(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); @@ -537,8 +988,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::new(task); do sched.event_loop.callback_ms(10) { rtdebug!("in callback"); @@ -546,9 +996,148 @@ mod test { sched.enqueue_task(task.take()); Local::put(sched); } - Local::put(sched); } } } } + + #[test] + fn handle() { + use rt::comm::*; + + do run_in_bare_thread { + let (port, chan) = oneshot::<()>(); + let port_cell = Cell::new(port); + let chan_cell = Cell::new(chan); + let mut sched1 = ~new_test_uv_sched(); + let handle1 = sched1.make_handle(); + let handle1_cell = Cell::new(handle1); + let task1 = ~do Task::new_root(&mut sched1.stack_pool) { + chan_cell.take().send(()); + }; + sched1.enqueue_task(task1); + + let mut sched2 = ~new_test_uv_sched(); + let task2 = ~do Task::new_root(&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::new(sched1); + let _thread1 = do Thread::start { + let sched1 = sched1_cell.take(); + sched1.run(); + }; + + let sched2_cell = Cell::new(sched2); + let _thread2 = do Thread::start { + let sched2 = sched2_cell.take(); + sched2.run(); + }; + } + } + + #[test] + fn multithreading() { + use rt::comm::*; + use iter::Times; + use vec::OwnedVector; + use container::Container; + + do run_in_mt_newsched_task { + let mut ports = ~[]; + for 10.times { + let (port, chan) = oneshot(); + let chan_cell = Cell::new(chan); + do spawntask_later { + chan_cell.take().send(()); + } + ports.push(port); + } + + while !ports.is_empty() { + ports.pop().recv(); + } + } + } + + #[test] + fn thread_ring() { + use rt::comm::*; + use comm::{GenericPort, GenericChan}; + + do run_in_mt_newsched_task { + let (end_port, end_chan) = oneshot(); + + let n_tasks = 10; + let token = 2000; + + let (p, ch1) = stream(); + let mut p = p; + 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; + } + } + } + } + } + } + + #[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 drop(&self) { + let _foo = @0; + } + } + + let s = S { field: () }; + + do spawntask { + let _ss = &s; + } + } + } } diff --git a/src/libstd/rt/sleeper_list.rs b/src/libstd/rt/sleeper_list.rs new file mode 100644 index 0000000000000..3d6e9ef5635e5 --- /dev/null +++ b/src/libstd/rt/sleeper_list.rs @@ -0,0 +1,59 @@ +// 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::SchedHandle; +use clone::Clone; + +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::new(handle); + unsafe { + self.stack.with(|s| s.push(handle.take())); + } + } + + pub fn pop(&mut self) -> Option { + unsafe { + do self.stack.with |s| { + if !s.is_empty() { + Some(s.pop()) + } else { + None + } + } + } + } +} + +impl Clone for SleeperList { + fn clone(&self) -> SleeperList { + SleeperList { + stack: self.stack.clone() + } + } +} \ No newline at end of file diff --git a/src/libstd/rt/task.rs b/src/libstd/rt/task.rs index f5f5aca71f55c..b4f4c1b3e35b9 100644 --- a/src/libstd/rt/task.rs +++ b/src/libstd/rt/task.rs @@ -15,20 +15,45 @@ use borrow; use cast::transmute; +use cleanup; use libc::{c_void, uintptr_t}; use ptr; use prelude::*; +use option::{Option, Some, None}; use rt::local::Local; use rt::logging::StdErrLogger; use super::local_heap::LocalHeap; +use rt::sched::{Scheduler, SchedHandle}; +use rt::join_latch::JoinLatch; +use rt::stack::{StackSegment, StackPool}; +use rt::context::Context; +use cell::Cell; pub struct Task { heap: LocalHeap, gc: GarbageCollector, storage: LocalStorage, logger: StdErrLogger, - unwinder: Option, - destroyed: bool + unwinder: Unwinder, + home: Option, + join_latch: Option<~JoinLatch>, + on_exit: Option<~fn(bool)>, + destroyed: bool, + coroutine: Option<~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, + /// Always valid if the task is alive and not running. + saved_context: Context +} + +pub enum SchedHome { + AnySched, + Sched(SchedHandle) } pub struct GarbageCollector; @@ -39,73 +64,227 @@ pub struct Unwinder { } impl Task { - pub fn new() -> Task { + + pub fn new_root(stack_pool: &mut StackPool, + start: ~fn()) -> Task { + Task::new_root_homed(stack_pool, AnySched, start) + } + + pub fn new_child(&mut self, + stack_pool: &mut StackPool, + start: ~fn()) -> Task { + self.new_child_homed(stack_pool, AnySched, start) + } + + pub fn new_root_homed(stack_pool: &mut StackPool, + home: SchedHome, + start: ~fn()) -> Task { Task { heap: LocalHeap::new(), gc: GarbageCollector, storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, - unwinder: Some(Unwinder { unwinding: false }), - destroyed: false + unwinder: Unwinder { unwinding: false }, + home: Some(home), + join_latch: Some(JoinLatch::new_root()), + on_exit: None, + destroyed: false, + coroutine: Some(~Coroutine::new(stack_pool, start)) } } - pub fn without_unwinding() -> Task { + pub fn new_child_homed(&mut self, + stack_pool: &mut StackPool, + home: SchedHome, + start: ~fn()) -> Task { Task { heap: LocalHeap::new(), gc: GarbageCollector, storage: LocalStorage(ptr::null(), None), logger: StdErrLogger, - unwinder: None, - destroyed: false + home: Some(home), + unwinder: Unwinder { unwinding: false }, + join_latch: Some(self.join_latch.get_mut_ref().new_child()), + on_exit: None, + destroyed: false, + coroutine: Some(~Coroutine::new(stack_pool, start)) } } + 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. - do Local::borrow:: |task| { + do Local::borrow:: |task| { assert!(borrow::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); + 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 => { - // Otherwise, just run the body - f() + join_latch.release(local_success); } } - self.destroy(); } - /// 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 /// thread-local-storage. 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!(borrow::ref_eq(task, self)); } + match self.storage { LocalStorage(ptr, Some(ref dtor)) => { (*dtor)(ptr) } _ => () } + + // Destroy remaining boxes + unsafe { cleanup::annihilate(); } + self.destroyed = true; } + + /// Check if *task* is currently home. + pub fn is_home(&self) -> bool { + do Local::borrow:: |sched| { + match self.home { + Some(AnySched) => { false } + Some(Sched(SchedHandle { sched_id: ref id, _ })) => { + *id == sched.sched_id() + } + None => { rtabort!("task home of None") } + } + } + } + + pub fn is_home_no_tls(&self, sched: &~Scheduler) -> bool { + match self.home { + Some(AnySched) => { false } + Some(Sched(SchedHandle { sched_id: ref id, _ })) => { + *id == sched.sched_id() + } + None => {rtabort!("task home of None") } + } + } + + pub fn is_home_using_id(sched_id: uint) -> bool { + do Local::borrow:: |task| { + match task.home { + Some(Sched(SchedHandle { sched_id: ref id, _ })) => { + *id == sched_id + } + Some(AnySched) => { false } + None => { rtabort!("task home of None") } + } + } + } + + /// Check if this *task* has a home. + pub fn homed(&self) -> bool { + match self.home { + Some(AnySched) => { false } + Some(Sched(_)) => { true } + None => { + rtabort!("task home of None") + } + } + } + + /// On a special scheduler? + pub fn on_special() -> bool { + do Local::borrow:: |sched| { + !sched.run_anything + } + } + } impl Drop for Task { fn drop(&self) { assert!(self.destroyed) } } +// Coroutines represent nothing more than a context and a stack +// segment. + +impl Coroutine { + + pub fn new(stack_pool: &mut StackPool, start: ~fn()) -> Coroutine { + static MIN_STACK_SIZE: uint = 100000; // XXX: Too much stack + + let start = Coroutine::build_start_wrapper(start); + let mut stack = stack_pool.take_segment(MIN_STACK_SIZE); + let initial_context = Context::new(start, &mut stack); + Coroutine { + current_stack_segment: stack, + saved_context: initial_context + } + } + + fn build_start_wrapper(start: ~fn()) -> ~fn() { + let start_cell = Cell::new(start); + let wrapper: ~fn() = || { + // First code after swap to this new context. Run our + // cleanup job. + unsafe { + let sched = Local::unsafe_borrow::(); + (*sched).run_cleanup_job(); + + let sched = Local::unsafe_borrow::(); + let task = (*sched).current_task.get_mut_ref(); + + do 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 the 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 our of + // scope while the task is still running. + let start = start_cell.take(); + start(); + }; + } + + let sched = Local::take::(); + sched.terminate_current_task(); + }; + return wrapper; + } + + /// Destroy coroutine and try to reuse stack segment. + pub fn recycle(~self, stack_pool: &mut StackPool) { + match self { + ~Coroutine { current_stack_segment, _ } => { + stack_pool.give_segment(current_stack_segment); + } + } + } + +} + + // Just a sanity check to make sure we are catching a Rust-thrown exception static UNWIND_TOKEN: uintptr_t = 839147; @@ -184,8 +363,10 @@ mod test { fn unwind() { do run_in_newsched_task() { let result = spawntask_try(||()); + rtdebug!("trying first assert"); assert!(result.is_ok()); let result = spawntask_try(|| fail!()); + rtdebug!("trying second assert"); assert!(result.is_err()); } } @@ -227,4 +408,67 @@ mod test { assert!(port.recv() == 10); } } + + #[test] + fn comm_shared_chan() { + use comm::*; + + do run_in_newsched_task() { + let (port, chan) = stream(); + let chan = SharedChan::new(chan); + chan.send(10); + 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()); + } + } + + #[test] + fn heap_cycles() { + use option::{Option, Some, None}; + + do run_in_newsched_task { + struct List { + next: Option<@mut List>, + } + + let a = @mut List { next: None }; + let b = @mut List { next: Some(a) }; + + a.next = Some(b); + } + } + + // XXX: This is a copy of test_future_result in std::task. + // It can be removed once the scheduler is turned on by default. + #[test] + fn future_result() { + do run_in_newsched_task { + use option::{Some, None}; + use task::*; + + let mut result = None; + let mut builder = task(); + builder.future_result(|r| result = Some(r)); + do builder.spawn {} + assert_eq!(result.unwrap().recv(), Success); + + result = None; + let mut builder = task(); + builder.future_result(|r| result = Some(r)); + builder.unlinked(); + do builder.spawn { + fail!(); + } + assert_eq!(result.unwrap().recv(), Failure); + } + } } diff --git a/src/libstd/rt/test.rs b/src/libstd/rt/test.rs index fe08d85c947c7..f4b9269e8cd48 100644 --- a/src/libstd/rt/test.rs +++ b/src/libstd/rt/test.rs @@ -8,75 +8,185 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use libc; use uint; -use option::*; +use option::{Some, None}; use cell::Cell; -use result::{Result, Ok, Err}; -use super::io::net::ip::{IpAddr, Ipv4}; -use rt::task::Task; -use rt::thread::Thread; +use clone::Clone; +use container::Container; +use iterator::IteratorUtil; +use vec::{OwnedVector, MutableVector}; +use super::io::net::ip::{IpAddr, Ipv4, Ipv6}; +use rt::sched::Scheduler; use rt::local::Local; +use unstable::run_in_bare_thread; +use rt::thread::Thread; +use rt::task::Task; +use rt::uv::uvio::UvEventLoop; +use rt::work_queue::WorkQueue; +use rt::sleeper_list::SleeperList; +use rt::task::{Sched}; +use rt::comm::oneshot; +use result::{Result, Ok, Err}; + +pub fn new_test_uv_sched() -> Scheduler { + + 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, /// then waits for the scheduler to exit. Failure of the task /// will abort the process. 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::new(f); do run_in_bare_thread { - let mut sched = ~UvEventLoop::new_scheduler(); - let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), - f.take()); + let mut sched = ~new_test_uv_sched(); + let on_exit: ~fn(bool) = |exit_status| rtassert!(exit_status); + let mut task = ~Task::new_root(&mut sched.stack_pool, + f.take()); + rtdebug!("newsched_task: %x", to_uint(task)); + task.on_exit = Some(on_exit); sched.enqueue_task(task); sched.run(); } } +/// 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 os; + use from_str::FromStr; + use rt::sched::Shutdown; + use rt::util; + + let f_cell = Cell::new(f); + + do run_in_bare_thread { + let nthreads = match os::getenv("RUST_TEST_THREADS") { + Some(nstr) => FromStr::from_str(nstr).get(), + None => { + // 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 f_cell = Cell::new(f_cell.take()); + let handles = Cell::new(handles); + 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); + }; + let mut main_task = ~Task::new_root(&mut scheds[0].stack_pool, + f_cell.take()); + main_task.on_exit = Some(on_exit); + 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; + } + +} + /// Test tasks will abort on failure instead of unwinding pub fn spawntask(f: ~fn()) { use super::sched::*; + let f = Cell::new(f); - let mut sched = Local::take::(); - 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::new(task); - let sched = Local::take::(); - sched.schedule_new_task(task.take()); - } + let task = unsafe { + let sched = Local::unsafe_borrow::(); + rtdebug!("spawntask taking the scheduler from TLS"); + + + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, f.take()) + } + }; + + rtdebug!("new task pointer: %x", to_uint(task)); + + let sched = Local::take::(); + rtdebug!("spawntask scheduling the new task"); + sched.schedule_task(task); } + /// Create a new task and run it right now. Aborts on failure pub fn spawntask_immediately(f: ~fn()) { use super::sched::*; - let mut sched = Local::take::(); - 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::new(task); - do Local::borrow:: |sched| { - sched.enqueue_task(task.take()); + let f = Cell::new(f); + + let task = unsafe { + let sched = Local::unsafe_borrow::(); + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, + f.take()) } + }; + + let sched = Local::take::(); + do sched.switch_running_tasks_and_then(task) |sched, task| { + sched.enqueue_task(task); } } /// Create a new task and run it right now. Aborts on failure pub fn spawntask_later(f: ~fn()) { use super::sched::*; + let f = Cell::new(f); - let mut sched = Local::take::(); - let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), - f); + let task = unsafe { + let sched = Local::unsafe_borrow::(); + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, f.take()) + } + }; + let mut sched = Local::take::(); sched.enqueue_task(task); Local::put(sched); } @@ -86,20 +196,25 @@ 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 f = Cell::new(f); + + let task = unsafe { + let sched = Local::unsafe_borrow::(); + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, + f.take()) + + } + }; let mut sched = Local::take::(); - let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), - f); + + let mut rng = rng(); + let run_now: bool = Rand::rand(&mut rng); if run_now { - do sched.switch_running_tasks_and_then(task) |task| { - let task = Cell::new(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); @@ -107,80 +222,165 @@ 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; -/// Spawn a task and wait for it to finish, returning whether it completed successfully or failed + // 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() + }; + + ~Task::new_root_homed(&mut sched.stack_pool, + Sched(handle), + af) + }; + let dest_sched = &mut scheds[rng.gen_int_range(0,scheds.len() as int)]; + // enqueue it for future execution + 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<(), ()> { 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; + let f = Cell::new(f); - // Switch to the scheduler - let f = Cell::new(Cell::new(f)); - let sched = Local::take::(); - do sched.deschedule_running_task_and_then() |old_task| { - let old_task = Cell::new(old_task); - let f = f.take(); - let mut sched = Local::take::(); - let new_task = ~do Coroutine::new(&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()) |new_task| { - let new_task = Cell::new(new_task); - do Local::borrow:: |sched| { - sched.enqueue_task(new_task.take()); - } - } - } - }; + let (port, chan) = oneshot(); + let chan = Cell::new(chan); + let on_exit: ~fn(bool) = |exit_status| chan.take().send(exit_status); + let mut new_task = unsafe { + let sched = Local::unsafe_borrow::(); + do Local::borrow:: |_running_task| { - sched.resume_task_immediately(new_task); + // I don't understand why using a child task here fails. I + // think the fail status is propogating back up the task + // tree and triggering a fail for the parent, which we + // aren't correctly expecting. + + // ~running_task.new_child(&mut (*sched).stack_pool, + ~Task::new_root(&mut (*sched).stack_pool, + f.take()) + } + }; + new_task.on_exit = Some(on_exit); + + let sched = Local::take::(); + do sched.switch_running_tasks_and_then(new_task) |sched, old_task| { + sched.enqueue_task(old_task); } - if !failed { Ok(()) } else { Err(()) } + rtdebug!("enqueued the new task, now waiting on exit_status"); + + let exit_status = port.recv(); + if exit_status { Ok(()) } else { Err(()) } } // 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::new(f); + + let task = unsafe { + let sched = Local::unsafe_borrow::(); + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, + f.take()) + } + }; + + let task = Cell::new(task); + let thread = do Thread::start { - let mut sched = ~UvEventLoop::new_scheduler(); - let task = ~Coroutine::with_task(&mut sched.stack_pool, - ~Task::without_unwinding(), - f.take()); - sched.enqueue_task(task); + let mut sched = ~new_test_uv_sched(); + sched.enqueue_task(task.take()); sched.run(); }; return thread; } + /// Get a port number, starting at 9600, for use in tests pub fn next_test_port() -> u16 { unsafe { - return rust_dbg_next_port() as u16; + return rust_dbg_next_port(base_port() as libc::uintptr_t) as u16; } extern { - fn rust_dbg_next_port() -> ::libc::uintptr_t; + fn rust_dbg_next_port(base: libc::uintptr_t) -> libc::uintptr_t; } } -/// Get a unique localhost:port pair starting at 9600 +/// Get a unique IPv4 localhost:port pair starting at 9600 pub fn next_test_ip4() -> IpAddr { Ipv4(127, 0, 0, 1, next_test_port()) } -/// Get a constant that represents the number of times to repeat stress tests. Default 1. +/// Get a unique IPv6 localhost:port pair starting at 9600 +pub fn next_test_ip6() -> IpAddr { + Ipv6(0, 0, 0, 0, 0, 0, 0, 1, next_test_port()) +} + +/* +XXX: Welcome to MegaHack City. + +The bots run multiple builds at the same time, and these builds +all want to use ports. This function figures out which workspace +it is running in and assigns a port range based on it. +*/ +fn base_port() -> uint { + use os; + use str::StrSlice; + use to_str::ToStr; + use vec::ImmutableVector; + + let base = 9600u; + let range = 1000; + + let bases = [ + ("32-opt", base + range * 1), + ("32-noopt", base + range * 2), + ("64-opt", base + range * 3), + ("64-noopt", base + range * 4), + ("64-opt-vg", base + range * 5), + ("all-opt", base + range * 6), + ("snap3", base + range * 7), + ("dist", base + range * 8) + ]; + + let path = os::getcwd().to_str(); + + let mut final_base = base; + + for bases.iter().advance |&(dir, base)| { + if path.contains(dir) { + final_base = base; + break; + } + } + + return final_base; +} + +/// Get a constant that represents the number of times to repeat +/// stress tests. Default 1. pub fn stress_factor() -> uint { use os::getenv; diff --git a/src/libstd/rt/tube.rs b/src/libstd/rt/tube.rs index 03e11dfad1d8d..013eb438c3657 100644 --- a/src/libstd/rt/tube.rs +++ b/src/libstd/rt/tube.rs @@ -16,14 +16,15 @@ use option::*; use clone::Clone; use super::rc::RC; -use rt::sched::{Scheduler, Coroutine}; +use rt::sched::Scheduler; use rt::{context, TaskContext, SchedulerContext}; use rt::local::Local; +use rt::task::Task; use vec::OwnedVector; use container::Container; struct TubeState { - blocked_task: Option<~Coroutine>, + blocked_task: Option<~Task>, buf: ~[T] } @@ -72,7 +73,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 +108,10 @@ mod test { let tube_clone = tube.clone(); let tube_clone_cell = Cell::new(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 +123,17 @@ mod test { do run_in_newsched_task { let mut tube: Tube = Tube::new(); let tube_clone = tube.clone(); - let tube_clone = Cell::new(Cell::new(Cell::new(tube_clone))); + let tube_clone = Cell::new(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::new(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,14 +149,14 @@ mod test { let tube_clone = tube.clone(); let tube_clone = Cell::new(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) { if i == 100 { return; } let tube = Cell::new(Cell::new(tube)); - do Local::borrow:: |sched| { + do Local::borrow:: |sched| { let tube = tube.take(); do sched.event_loop.callback { let mut tube = tube.take(); @@ -172,8 +168,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/libstd/rt/util.rs b/src/libstd/rt/util.rs new file mode 100644 index 0000000000000..a1169954688b0 --- /dev/null +++ b/src/libstd/rt/util.rs @@ -0,0 +1,121 @@ +// 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 container::Container; +use from_str::FromStr; +use iterator::IteratorUtil; +use libc; +use option::{Some, None}; +use os; +use str::StrSlice; + +/// 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; + } +} + +/// Get's the number of scheduler threads requested by the environment +/// either `RUST_THREADS` or `num_cpus`. +pub fn default_sched_threads() -> uint { + match os::getenv("RUST_THREADS") { + Some(nstr) => FromStr::from_str(nstr).get(), + None => num_cpus() + } +} + +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(); } +} + +pub fn set_exit_status(code: int) { + + unsafe { + return rust_set_exit_status_newrt(code as libc::uintptr_t); + } + + extern { + fn rust_set_exit_status_newrt(code: libc::uintptr_t); + } +} + +pub fn get_exit_status() -> int { + + unsafe { + return rust_get_exit_status_newrt() as int; + } + + extern { + fn rust_get_exit_status_newrt() -> libc::uintptr_t; + } +} \ No newline at end of file diff --git a/src/libstd/rt/uv/async.rs b/src/libstd/rt/uv/async.rs new file mode 100644 index 0000000000000..f3d1024024ff8 --- /dev/null +++ b/src/libstd/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 { + pub 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); + } + } + + pub fn send(&mut self) { + unsafe { + let handle = self.native_handle(); + uvll::async_send(handle); + } + } + + pub 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::new(watcher); + let _thread = do Thread::start { + let mut watcher = watcher_cell.take(); + watcher.send(); + }; + loop_.run(); + loop_.close(); + } + } +} diff --git a/src/libstd/rt/uv/idle.rs b/src/libstd/rt/uv/idle.rs index e1def9ffd508b..a3630c9b9bf8d 100644 --- a/src/libstd/rt/uv/idle.rs +++ b/src/libstd/rt/uv/idle.rs @@ -90,3 +90,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/libstd/rt/uv/mod.rs b/src/libstd/rt/uv/mod.rs index 092d736620268..0eaf0dd3ab649 100644 --- a/src/libstd/rt/uv/mod.rs +++ b/src/libstd/rt/uv/mod.rs @@ -47,15 +47,17 @@ use libc::{c_void, c_int, size_t, malloc, free}; use cast::transmute; use ptr::null; use unstable::finally::Finally; +use rt::io::net::ip::IpAddr; use rt::io::IoError; #[cfg(test)] use unstable::run_in_bare_thread; pub use self::file::FsRequest; -pub use self::net::{StreamWatcher, TcpWatcher}; +pub use self::net::{StreamWatcher, TcpWatcher, UdpWatcher}; pub use self::idle::IdleWatcher; pub use self::timer::TimerWatcher; +pub use self::async::AsyncWatcher; /// The implementation of `rtio` for libuv pub mod uvio; @@ -67,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, @@ -124,6 +127,9 @@ 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); +pub type UdpReceiveCallback = ~fn(UdpWatcher, int, Buf, IpAddr, uint, Option); +pub type UdpSendCallback = ~fn(UdpWatcher, Option); /// Callbacks used by StreamWatchers, set as custom data on the foreign handle @@ -134,7 +140,10 @@ struct WatcherData { close_cb: Option, alloc_cb: Option, idle_cb: Option, - timer_cb: Option + timer_cb: Option, + async_cb: Option, + udp_recv_cb: Option, + udp_send_cb: Option } pub trait WatcherInterop { @@ -163,7 +172,10 @@ impl> WatcherInterop for W { close_cb: None, alloc_cb: None, idle_cb: None, - timer_cb: None + timer_cb: None, + async_cb: None, + udp_recv_cb: None, + udp_send_cb: None }; let data = transmute::<~WatcherData, *c_void>(data); uvll::set_data_for_uv_handle(self.native_handle(), data); @@ -348,57 +360,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(); - } -} diff --git a/src/libstd/rt/uv/net.rs b/src/libstd/rt/uv/net.rs index ada9aee35a7e3..6d096f9885a7d 100644 --- a/src/libstd/rt/uv/net.rs +++ b/src/libstd/rt/uv/net.rs @@ -9,35 +9,151 @@ // except according to those terms. use prelude::*; -use libc::{size_t, ssize_t, c_int, c_void}; +use libc::{size_t, ssize_t, c_int, c_void, c_uint}; use rt::uv::uvll; use rt::uv::uvll::*; -use rt::uv::{AllocCallback, ConnectionCallback, ReadCallback}; +use rt::uv::{AllocCallback, ConnectionCallback, ReadCallback, UdpReceiveCallback, UdpSendCallback}; use rt::uv::{Loop, Watcher, Request, UvError, Buf, NativeHandle, NullCallback, status_to_maybe_uv_error}; use rt::io::net::ip::{IpAddr, Ipv4, Ipv6}; use rt::uv::last_uv_error; +use vec; +use str; +use from_str::{FromStr}; +use num; + +enum UvIpAddr { + UvIpv4(*sockaddr_in), + UvIpv6(*sockaddr_in6), +} -fn ip4_as_uv_ip4(addr: IpAddr, f: &fn(*sockaddr_in) -> T) -> T { - match addr { - Ipv4(a, b, c, d, p) => { - unsafe { - let addr = malloc_ip4_addr(fmt!("%u.%u.%u.%u", - a as uint, - b as uint, - c as uint, - d as uint), p as int); - do (|| { - f(addr) - }).finally { - free_ip4_addr(addr); - } - } +fn sockaddr_to_UvIpAddr(addr: *uvll::sockaddr) -> UvIpAddr { + unsafe { + assert!((is_ip4_addr(addr) || is_ip6_addr(addr))); + assert!(!(is_ip4_addr(addr) && is_ip6_addr(addr))); + match addr { + _ if is_ip4_addr(addr) => UvIpv4(as_sockaddr_in(addr)), + _ if is_ip6_addr(addr) => UvIpv6(as_sockaddr_in6(addr)), + _ => fail!(), } - Ipv6 => fail!() } } +fn ip_as_uv_ip(addr: IpAddr, f: &fn(UvIpAddr) -> T) -> T { + let malloc = match addr { + Ipv4(*) => malloc_ip4_addr, + Ipv6(*) => malloc_ip6_addr, + }; + let wrap = match addr { + Ipv4(*) => UvIpv4, + Ipv6(*) => UvIpv6, + }; + let ip_str = match addr { + Ipv4(x1, x2, x3, x4, _) => + fmt!("%u.%u.%u.%u", x1 as uint, x2 as uint, x3 as uint, x4 as uint), + Ipv6(x1, x2, x3, x4, x5, x6, x7, x8, _) => + fmt!("%x:%x:%x:%x:%x:%x:%x:%x", + x1 as uint, x2 as uint, x3 as uint, x4 as uint, + x5 as uint, x6 as uint, x7 as uint, x8 as uint), + }; + let port = match addr { + Ipv4(_, _, _, _, p) | Ipv6(_, _, _, _, _, _, _, _, p) => p as int + }; + let free = match addr { + Ipv4(*) => free_ip4_addr, + Ipv6(*) => free_ip6_addr, + }; + + let addr = unsafe { malloc(ip_str, port) }; + do (|| { + f(wrap(addr)) + }).finally { + unsafe { free(addr) }; + } +} + +fn uv_ip_as_ip(addr: UvIpAddr, f: &fn(IpAddr) -> T) -> T { + let ip_size = match addr { + UvIpv4(*) => 4/*groups of*/ * 3/*digits separated by*/ + 3/*periods*/, + UvIpv6(*) => 8/*groups of*/ * 4/*hex digits separated by*/ + 7 /*colons*/, + }; + let ip_name = { + let buf = vec::from_elem(ip_size + 1 /*null terminated*/, 0u8); + unsafe { + match addr { + UvIpv4(addr) => uvll::ip4_name(addr, vec::raw::to_ptr(buf), ip_size as size_t), + UvIpv6(addr) => uvll::ip6_name(addr, vec::raw::to_ptr(buf), ip_size as size_t), + } + }; + buf + }; + let ip_port = unsafe { + let port = match addr { + UvIpv4(addr) => uvll::ip4_port(addr), + UvIpv6(addr) => uvll::ip6_port(addr), + }; + port as u16 + }; + let ip_str = str::from_bytes_slice(ip_name).trim_right_chars(&'\x00'); + let ip = match addr { + UvIpv4(*) => { + let ip: ~[u8] = + ip_str.split_iter('.') + .transform(|s: &str| -> u8 { FromStr::from_str(s).unwrap() }) + .collect(); + assert_eq!(ip.len(), 4); + Ipv4(ip[0], ip[1], ip[2], ip[3], ip_port) + }, + UvIpv6(*) => { + let ip: ~[u16] = { + let expand_shorthand_and_convert = |s: &str| -> ~[~[u16]] { + let convert_each_segment = |s: &str| -> ~[u16] { + let read_hex_segment = |s: &str| -> u16 { + num::FromStrRadix::from_str_radix(s, 16u).unwrap() + }; + match s { + "" => ~[], + s => s.split_iter(':').transform(read_hex_segment).collect(), + } + }; + s.split_str_iter("::").transform(convert_each_segment).collect() + }; + match expand_shorthand_and_convert(ip_str) { + [x] => x, // no shorthand found + [l, r] => l + vec::from_elem(8 - l.len() - r.len(), 0u16) + r, // fill the gap + _ => fail!(), // impossible. only one shorthand allowed. + } + }; + assert_eq!(ip.len(), 8); + Ipv6(ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip_port) + }, + }; + + // finally run the closure + f(ip) +} + +fn uv_ip_to_ip(addr: UvIpAddr) -> IpAddr { + use util; + uv_ip_as_ip(addr, util::id) +} + +#[cfg(test)] +#[test] +fn test_ip4_conversion() { + use rt; + let ip4 = rt::test::next_test_ip4(); + assert_eq!(ip4, ip_as_uv_ip(ip4, uv_ip_to_ip)); +} + +#[cfg(test)] +#[test] +fn test_ip6_conversion() { + use rt; + let ip6 = rt::test::next_test_ip6(); + assert_eq!(ip6, ip_as_uv_ip(ip6, uv_ip_to_ip)); +} + // uv_stream t is the parent class of uv_tcp_t, uv_pipe_t, uv_tty_t // and uv_file_t pub struct StreamWatcher(*uvll::uv_stream_t); @@ -51,13 +167,11 @@ impl StreamWatcher { data.read_cb = Some(cb); } - let handle = self.native_handle(); - unsafe { uvll::read_start(handle, alloc_cb, read_cb); } + unsafe { uvll::read_start(self.native_handle(), alloc_cb, read_cb); } extern fn alloc_cb(stream: *uvll::uv_stream_t, suggested_size: size_t) -> Buf { let mut stream_watcher: StreamWatcher = NativeHandle::from_native_handle(stream); - let data = stream_watcher.get_watcher_data(); - let alloc_cb = data.alloc_cb.get_ref(); + let alloc_cb = stream_watcher.get_watcher_data().alloc_cb.get_ref(); return (*alloc_cb)(suggested_size as uint); } @@ -65,8 +179,7 @@ impl StreamWatcher { rtdebug!("buf addr: %x", buf.base as uint); rtdebug!("buf len: %d", buf.len as int); let mut stream_watcher: StreamWatcher = NativeHandle::from_native_handle(stream); - let data = stream_watcher.get_watcher_data(); - let cb = data.read_cb.get_ref(); + let cb = stream_watcher.get_watcher_data().read_cb.get_ref(); let status = status_to_maybe_uv_error(stream, nread as c_int); (*cb)(stream_watcher, nread as int, buf, status); } @@ -88,22 +201,15 @@ impl StreamWatcher { } let req = WriteRequest::new(); - let bufs = [buf]; unsafe { - assert!(0 == uvll::write(req.native_handle(), - self.native_handle(), - bufs, write_cb)); + assert_eq!(0, uvll::write(req.native_handle(), self.native_handle(), [buf], write_cb)); } extern fn write_cb(req: *uvll::uv_write_t, status: c_int) { let write_request: WriteRequest = NativeHandle::from_native_handle(req); let mut stream_watcher = write_request.stream(); write_request.delete(); - let cb = { - let data = stream_watcher.get_watcher_data(); - let cb = data.write_cb.swap_unwrap(); - cb - }; + let cb = stream_watcher.get_watcher_data().write_cb.swap_unwrap(); let status = status_to_maybe_uv_error(stream_watcher.native_handle(), status); cb(stream_watcher, status); } @@ -112,9 +218,7 @@ impl StreamWatcher { pub fn accept(&mut self, stream: StreamWatcher) { let self_handle = self.native_handle() as *c_void; let stream_handle = stream.native_handle() as *c_void; - unsafe { - assert_eq!(0, uvll::accept(self_handle, stream_handle)); - } + assert_eq!(0, unsafe { uvll::accept(self_handle, stream_handle) } ); } pub fn close(self, cb: NullCallback) { @@ -129,10 +233,7 @@ impl StreamWatcher { extern fn close_cb(handle: *uvll::uv_stream_t) { let mut stream_watcher: StreamWatcher = NativeHandle::from_native_handle(handle); - { - let data = stream_watcher.get_watcher_data(); - data.close_cb.swap_unwrap()(); - } + stream_watcher.get_watcher_data().close_cb.swap_unwrap()(); stream_watcher.drop_watcher_data(); unsafe { free_handle(handle as *c_void) } } @@ -140,8 +241,7 @@ impl StreamWatcher { } impl NativeHandle<*uvll::uv_stream_t> for StreamWatcher { - fn from_native_handle( - handle: *uvll::uv_stream_t) -> StreamWatcher { + fn from_native_handle(handle: *uvll::uv_stream_t) -> StreamWatcher { StreamWatcher(handle) } fn native_handle(&self) -> *uvll::uv_stream_t { @@ -153,7 +253,7 @@ pub struct TcpWatcher(*uvll::uv_tcp_t); impl Watcher for TcpWatcher { } impl TcpWatcher { - pub fn new(loop_: &mut Loop) -> TcpWatcher { + pub fn new(loop_: &Loop) -> TcpWatcher { unsafe { let handle = malloc_handle(UV_TCP); assert!(handle.is_not_null()); @@ -165,20 +265,17 @@ impl TcpWatcher { } pub fn bind(&mut self, address: IpAddr) -> Result<(), UvError> { - match address { - Ipv4(*) => { - do ip4_as_uv_ip4(address) |addr| { - let result = unsafe { - uvll::tcp_bind(self.native_handle(), addr) - }; - if result == 0 { - Ok(()) - } else { - Err(last_uv_error(self)) - } + do ip_as_uv_ip(address) |addr| { + let result = unsafe { + match addr { + UvIpv4(addr) => uvll::tcp_bind(self.native_handle(), addr), + UvIpv6(addr) => uvll::tcp_bind6(self.native_handle(), addr), } + }; + match result { + 0 => Ok(()), + _ => Err(last_uv_error(self)), } - _ => fail!() } } @@ -188,16 +285,15 @@ impl TcpWatcher { self.get_watcher_data().connect_cb = Some(cb); let connect_handle = ConnectRequest::new().native_handle(); - match address { - Ipv4(*) => { - do ip4_as_uv_ip4(address) |addr| { - rtdebug!("connect_t: %x", connect_handle as uint); - assert!(0 == uvll::tcp_connect(connect_handle, - self.native_handle(), - addr, connect_cb)); - } - } - _ => fail!() + rtdebug!("connect_t: %x", connect_handle as uint); + do ip_as_uv_ip(address) |addr| { + let result = match addr { + UvIpv4(addr) => uvll::tcp_connect(connect_handle, + self.native_handle(), addr, connect_cb), + UvIpv6(addr) => uvll::tcp_connect6(connect_handle, + self.native_handle(), addr, connect_cb), + }; + assert_eq!(0, result); } extern fn connect_cb(req: *uvll::uv_connect_t, status: c_int) { @@ -205,10 +301,7 @@ impl TcpWatcher { let connect_request: ConnectRequest = NativeHandle::from_native_handle(req); let mut stream_watcher = connect_request.stream(); connect_request.delete(); - let cb: ConnectionCallback = { - let data = stream_watcher.get_watcher_data(); - data.connect_cb.swap_unwrap() - }; + let cb = stream_watcher.get_watcher_data().connect_cb.swap_unwrap(); let status = status_to_maybe_uv_error(stream_watcher.native_handle(), status); cb(stream_watcher, status); } @@ -225,15 +318,13 @@ impl TcpWatcher { unsafe { static BACKLOG: c_int = 128; // XXX should be configurable // XXX: This can probably fail - assert!(0 == uvll::listen(self.native_handle(), - BACKLOG, connection_cb)); + assert_eq!(0, uvll::listen(self.native_handle(), BACKLOG, connection_cb)); } extern fn connection_cb(handle: *uvll::uv_stream_t, status: c_int) { rtdebug!("connection_cb"); let mut stream_watcher: StreamWatcher = NativeHandle::from_native_handle(handle); - let data = stream_watcher.get_watcher_data(); - let cb = data.connect_cb.get_ref(); + let cb = stream_watcher.get_watcher_data().connect_cb.get_ref(); let status = status_to_maybe_uv_error(handle, status); (*cb)(stream_watcher, status); } @@ -253,6 +344,134 @@ impl NativeHandle<*uvll::uv_tcp_t> for TcpWatcher { } } +pub struct UdpWatcher(*uvll::uv_udp_t); +impl Watcher for UdpWatcher { } + +impl UdpWatcher { + pub fn new(loop_: &Loop) -> UdpWatcher { + unsafe { + let handle = malloc_handle(UV_UDP); + assert!(handle.is_not_null()); + assert_eq!(0, uvll::udp_init(loop_.native_handle(), handle)); + let mut watcher: UdpWatcher = NativeHandle::from_native_handle(handle); + watcher.install_watcher_data(); + return watcher; + } + } + + pub fn bind(&self, address: IpAddr) -> Result<(), UvError> { + do ip_as_uv_ip(address) |addr| { + let result = unsafe { + match addr { + UvIpv4(addr) => uvll::udp_bind(self.native_handle(), addr, 0u32), + UvIpv6(addr) => uvll::udp_bind6(self.native_handle(), addr, 0u32), + } + }; + match result { + 0 => Ok(()), + _ => Err(last_uv_error(self)), + } + } + } + + pub fn recv_start(&self, alloc: AllocCallback, cb: UdpReceiveCallback) { + { + let mut this = *self; + let data = this.get_watcher_data(); + data.alloc_cb = Some(alloc); + data.udp_recv_cb = Some(cb); + } + + unsafe { uvll::udp_recv_start(self.native_handle(), alloc_cb, recv_cb); } + + extern fn alloc_cb(handle: *uvll::uv_udp_t, suggested_size: size_t) -> Buf { + let mut udp_watcher: UdpWatcher = NativeHandle::from_native_handle(handle); + let alloc_cb = udp_watcher.get_watcher_data().alloc_cb.get_ref(); + return (*alloc_cb)(suggested_size as uint); + } + + extern fn recv_cb(handle: *uvll::uv_udp_t, nread: ssize_t, buf: Buf, + addr: *uvll::sockaddr, flags: c_uint) { + // When there's no data to read the recv callback can be a no-op. + // This can happen if read returns EAGAIN/EWOULDBLOCK. By ignoring + // this we just drop back to kqueue and wait for the next callback. + if nread == 0 { + return; + } + + rtdebug!("buf addr: %x", buf.base as uint); + rtdebug!("buf len: %d", buf.len as int); + let mut udp_watcher: UdpWatcher = NativeHandle::from_native_handle(handle); + let cb = udp_watcher.get_watcher_data().udp_recv_cb.get_ref(); + let status = status_to_maybe_uv_error(handle, nread as c_int); + let addr = uv_ip_to_ip(sockaddr_to_UvIpAddr(addr)); + (*cb)(udp_watcher, nread as int, buf, addr, flags as uint, status); + } + } + + pub fn recv_stop(&self) { + unsafe { uvll::udp_recv_stop(self.native_handle()); } + } + + pub fn send(&self, buf: Buf, address: IpAddr, cb: UdpSendCallback) { + { + let mut this = *self; + let data = this.get_watcher_data(); + assert!(data.udp_send_cb.is_none()); + data.udp_send_cb = Some(cb); + } + + let req = UdpSendRequest::new(); + do ip_as_uv_ip(address) |addr| { + let result = unsafe { + match addr { + UvIpv4(addr) => uvll::udp_send(req.native_handle(), + self.native_handle(), [buf], addr, send_cb), + UvIpv6(addr) => uvll::udp_send6(req.native_handle(), + self.native_handle(), [buf], addr, send_cb), + } + }; + assert_eq!(0, result); + } + + extern fn send_cb(req: *uvll::uv_udp_send_t, status: c_int) { + let send_request: UdpSendRequest = NativeHandle::from_native_handle(req); + let mut udp_watcher = send_request.handle(); + send_request.delete(); + let cb = udp_watcher.get_watcher_data().udp_send_cb.swap_unwrap(); + let status = status_to_maybe_uv_error(udp_watcher.native_handle(), status); + cb(udp_watcher, status); + } + } + + pub 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_udp_t) { + let mut udp_watcher: UdpWatcher = NativeHandle::from_native_handle(handle); + udp_watcher.get_watcher_data().close_cb.swap_unwrap()(); + udp_watcher.drop_watcher_data(); + unsafe { free_handle(handle as *c_void) } + } + } +} + +impl NativeHandle<*uvll::uv_udp_t> for UdpWatcher { + fn from_native_handle(handle: *uvll::uv_udp_t) -> UdpWatcher { + UdpWatcher(handle) + } + fn native_handle(&self) -> *uvll::uv_udp_t { + match self { &UdpWatcher(ptr) => ptr } + } +} + // uv_connect_t is a subclass of uv_req_t struct ConnectRequest(*uvll::uv_connect_t); impl Request for ConnectRequest { } @@ -260,12 +479,9 @@ impl Request for ConnectRequest { } impl ConnectRequest { fn new() -> ConnectRequest { - let connect_handle = unsafe { - malloc_req(UV_CONNECT) - }; + let connect_handle = unsafe { malloc_req(UV_CONNECT) }; assert!(connect_handle.is_not_null()); - let connect_handle = connect_handle as *uvll::uv_connect_t; - ConnectRequest(connect_handle) + ConnectRequest(connect_handle as *uvll::uv_connect_t) } fn stream(&self) -> StreamWatcher { @@ -281,8 +497,7 @@ impl ConnectRequest { } impl NativeHandle<*uvll::uv_connect_t> for ConnectRequest { - fn from_native_handle( - handle: *uvll:: uv_connect_t) -> ConnectRequest { + fn from_native_handle(handle: *uvll:: uv_connect_t) -> ConnectRequest { ConnectRequest(handle) } fn native_handle(&self) -> *uvll::uv_connect_t { @@ -296,12 +511,9 @@ impl Request for WriteRequest { } impl WriteRequest { pub fn new() -> WriteRequest { - let write_handle = unsafe { - malloc_req(UV_WRITE) - }; + let write_handle = unsafe { malloc_req(UV_WRITE) }; assert!(write_handle.is_not_null()); - let write_handle = write_handle as *uvll::uv_write_t; - WriteRequest(write_handle) + WriteRequest(write_handle as *uvll::uv_write_t) } pub fn stream(&self) -> StreamWatcher { @@ -325,6 +537,36 @@ impl NativeHandle<*uvll::uv_write_t> for WriteRequest { } } +pub struct UdpSendRequest(*uvll::uv_udp_send_t); +impl Request for UdpSendRequest { } + +impl UdpSendRequest { + pub fn new() -> UdpSendRequest { + let send_handle = unsafe { malloc_req(UV_UDP_SEND) }; + assert!(send_handle.is_not_null()); + UdpSendRequest(send_handle as *uvll::uv_udp_send_t) + } + + pub fn handle(&self) -> UdpWatcher { + let send_request_handle = unsafe { + uvll::get_udp_handle_from_send_req(self.native_handle()) + }; + NativeHandle::from_native_handle(send_request_handle) + } + + pub fn delete(self) { + unsafe { free_req(self.native_handle() as *c_void) } + } +} + +impl NativeHandle<*uvll::uv_udp_send_t> for UdpSendRequest { + fn from_native_handle(handle: *uvll::uv_udp_send_t) -> UdpSendRequest { + UdpSendRequest(handle) + } + fn native_handle(&self) -> *uvll::uv_udp_send_t { + match self { &UdpSendRequest(ptr) => ptr } + } +} #[cfg(test)] mod test { @@ -339,7 +581,7 @@ mod test { use rt::uv::{vec_from_uv_buf, vec_to_uv_buf, slice_to_uv_buf}; #[test] - fn connect_close() { + fn connect_close_ip4() { do run_in_bare_thread() { let mut loop_ = Loop::new(); let mut tcp_watcher = { TcpWatcher::new(&mut loop_) }; @@ -357,7 +599,51 @@ mod test { } #[test] - fn listen() { + fn connect_close_ip6() { + do run_in_bare_thread() { + let mut loop_ = Loop::new(); + let mut tcp_watcher = { TcpWatcher::new(&mut loop_) }; + // Connect to a port where nobody is listening + let addr = next_test_ip6(); + do tcp_watcher.connect(addr) |stream_watcher, status| { + rtdebug!("tcp_watcher.connect!"); + assert!(status.is_some()); + assert_eq!(status.get().name(), ~"ECONNREFUSED"); + stream_watcher.close(||()); + } + loop_.run(); + loop_.close(); + } + } + + #[test] + fn udp_bind_close_ip4() { + do run_in_bare_thread() { + let mut loop_ = Loop::new(); + let udp_watcher = { UdpWatcher::new(&mut loop_) }; + let addr = next_test_ip4(); + udp_watcher.bind(addr); + udp_watcher.close(||()); + loop_.run(); + loop_.close(); + } + } + + #[test] + fn udp_bind_close_ip6() { + do run_in_bare_thread() { + let mut loop_ = Loop::new(); + let udp_watcher = { UdpWatcher::new(&mut loop_) }; + let addr = next_test_ip6(); + udp_watcher.bind(addr); + udp_watcher.close(||()); + loop_.run(); + loop_.close(); + } + } + + #[test] + fn listen_ip4() { do run_in_bare_thread() { static MAX: int = 10; let mut loop_ = Loop::new(); @@ -366,10 +652,82 @@ mod test { server_tcp_watcher.bind(addr); let loop_ = loop_; rtdebug!("listening"); - do server_tcp_watcher.listen |server_stream_watcher, status| { + do server_tcp_watcher.listen |mut server_stream_watcher, status| { + rtdebug!("listened!"); + assert!(status.is_none()); + let mut loop_ = loop_; + let client_tcp_watcher = TcpWatcher::new(&mut loop_); + let mut client_tcp_watcher = client_tcp_watcher.as_stream(); + server_stream_watcher.accept(client_tcp_watcher); + let count_cell = Cell::new(0); + let server_stream_watcher = server_stream_watcher; + rtdebug!("starting read"); + let alloc: AllocCallback = |size| { + vec_to_uv_buf(vec::from_elem(size, 0)) + }; + do client_tcp_watcher.read_start(alloc) |stream_watcher, nread, buf, status| { + + rtdebug!("i'm reading!"); + let buf = vec_from_uv_buf(buf); + let mut count = count_cell.take(); + if status.is_none() { + rtdebug!("got %d bytes", nread); + let buf = buf.unwrap(); + for buf.slice(0, nread as uint).iter().advance() |byte| { + assert!(*byte == count as u8); + rtdebug!("%u", *byte as uint); + count += 1; + } + } else { + assert_eq!(count, MAX); + do stream_watcher.close { + server_stream_watcher.close(||()); + } + } + count_cell.put_back(count); + } + } + + let _client_thread = do Thread::start { + rtdebug!("starting client thread"); + let mut loop_ = Loop::new(); + let mut tcp_watcher = { TcpWatcher::new(&mut loop_) }; + do tcp_watcher.connect(addr) |mut stream_watcher, status| { + rtdebug!("connecting"); + assert!(status.is_none()); + let msg = ~[0, 1, 2, 3, 4, 5, 6 ,7 ,8, 9]; + let buf = slice_to_uv_buf(msg); + let msg_cell = Cell::new(msg); + do stream_watcher.write(buf) |stream_watcher, status| { + rtdebug!("writing"); + assert!(status.is_none()); + let msg_cell = Cell::new(msg_cell.take()); + stream_watcher.close(||ignore(msg_cell.take())); + } + } + loop_.run(); + loop_.close(); + }; + + let mut loop_ = loop_; + loop_.run(); + loop_.close(); + } + } + + #[test] + fn listen_ip6() { + do run_in_bare_thread() { + static MAX: int = 10; + let mut loop_ = Loop::new(); + let mut server_tcp_watcher = { TcpWatcher::new(&mut loop_) }; + let addr = next_test_ip6(); + server_tcp_watcher.bind(addr); + let loop_ = loop_; + rtdebug!("listening"); + do server_tcp_watcher.listen |mut server_stream_watcher, status| { rtdebug!("listened!"); assert!(status.is_none()); - let mut server_stream_watcher = server_stream_watcher; let mut loop_ = loop_; let client_tcp_watcher = TcpWatcher::new(&mut loop_); let mut client_tcp_watcher = client_tcp_watcher.as_stream(); @@ -409,10 +767,9 @@ mod test { rtdebug!("starting client thread"); let mut loop_ = Loop::new(); let mut tcp_watcher = { TcpWatcher::new(&mut loop_) }; - do tcp_watcher.connect(addr) |stream_watcher, status| { + do tcp_watcher.connect(addr) |mut stream_watcher, status| { rtdebug!("connecting"); assert!(status.is_none()); - let mut stream_watcher = stream_watcher; let msg = ~[0, 1, 2, 3, 4, 5, 6 ,7 ,8, 9]; let buf = slice_to_uv_buf(msg); let msg_cell = Cell::new(msg); @@ -432,4 +789,122 @@ mod test { loop_.close(); } } + + #[test] + fn udp_recv_ip4() { + do run_in_bare_thread() { + static MAX: int = 10; + let mut loop_ = Loop::new(); + let server_addr = next_test_ip4(); + let client_addr = next_test_ip4(); + + let server = UdpWatcher::new(&loop_); + assert!(server.bind(server_addr).is_ok()); + + rtdebug!("starting read"); + let alloc: AllocCallback = |size| { + vec_to_uv_buf(vec::from_elem(size, 0)) + }; + + do server.recv_start(alloc) |server, nread, buf, src, flags, status| { + server.recv_stop(); + rtdebug!("i'm reading!"); + assert!(status.is_none()); + assert_eq!(flags, 0); + assert_eq!(src, client_addr); + + let buf = vec_from_uv_buf(buf); + let mut count = 0; + rtdebug!("got %d bytes", nread); + + let buf = buf.unwrap(); + for buf.slice(0, nread as uint).iter().advance() |&byte| { + assert!(byte == count as u8); + rtdebug!("%u", byte as uint); + count += 1; + } + assert_eq!(count, MAX); + + server.close(||{}); + } + + do Thread::start { + let mut loop_ = Loop::new(); + let client = UdpWatcher::new(&loop_); + assert!(client.bind(client_addr).is_ok()); + let msg = ~[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let buf = slice_to_uv_buf(msg); + do client.send(buf, server_addr) |client, status| { + rtdebug!("writing"); + assert!(status.is_none()); + client.close(||{}); + } + + loop_.run(); + loop_.close(); + }; + + loop_.run(); + loop_.close(); + } + } + + #[test] + fn udp_recv_ip6() { + do run_in_bare_thread() { + static MAX: int = 10; + let mut loop_ = Loop::new(); + let server_addr = next_test_ip6(); + let client_addr = next_test_ip6(); + + let server = UdpWatcher::new(&loop_); + assert!(server.bind(server_addr).is_ok()); + + rtdebug!("starting read"); + let alloc: AllocCallback = |size| { + vec_to_uv_buf(vec::from_elem(size, 0)) + }; + + do server.recv_start(alloc) |server, nread, buf, src, flags, status| { + server.recv_stop(); + rtdebug!("i'm reading!"); + assert!(status.is_none()); + assert_eq!(flags, 0); + assert_eq!(src, client_addr); + + let buf = vec_from_uv_buf(buf); + let mut count = 0; + rtdebug!("got %d bytes", nread); + + let buf = buf.unwrap(); + for buf.slice(0, nread as uint).iter().advance() |&byte| { + assert!(byte == count as u8); + rtdebug!("%u", byte as uint); + count += 1; + } + assert_eq!(count, MAX); + + server.close(||{}); + } + + do Thread::start { + let mut loop_ = Loop::new(); + let client = UdpWatcher::new(&loop_); + assert!(client.bind(client_addr).is_ok()); + let msg = ~[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; + let buf = slice_to_uv_buf(msg); + do client.send(buf, server_addr) |client, status| { + rtdebug!("writing"); + assert!(status.is_none()); + client.close(||{}); + } + + loop_.run(); + loop_.close(); + }; + + loop_.run(); + loop_.close(); + } + } } diff --git a/src/libstd/rt/uv/uvio.rs b/src/libstd/rt/uv/uvio.rs index 15c405bae6952..5d0c64c686782 100644 --- a/src/libstd/rt/uv/uvio.rs +++ b/src/libstd/rt/uv/uvio.rs @@ -12,6 +12,7 @@ use option::*; use result::*; use ops::Drop; use cell::Cell; +use cast; use cast::transmute; use clone::Clone; use rt::io::IoError; @@ -23,11 +24,15 @@ use rt::sched::Scheduler; use rt::io::{standard_error, OtherIoError}; use rt::tube::Tube; use rt::local::Local; +use unstable::sync::{Exclusive, exclusive}; #[cfg(test)] use container::Container; #[cfg(test)] use uint; #[cfg(test)] use unstable::run_in_bare_thread; -#[cfg(test)] use rt::test::*; +#[cfg(test)] use rt::test::{spawntask_immediately, + next_test_ip4, + run_in_newsched_task}; + pub struct UvEventLoop { uvio: UvIoFactory @@ -39,11 +44,6 @@ impl UvEventLoop { uvio: UvIoFactory(Loop::new()) } } - - /// A convenience constructor - pub fn new_scheduler() -> Scheduler { - Scheduler::new(~UvEventLoop::new()) - } } impl Drop for UvEventLoop { @@ -63,9 +63,8 @@ impl EventLoop for UvEventLoop { fn callback(&mut self, f: ~fn()) { let mut idle_watcher = IdleWatcher::new(self.uvio.uv_loop()); - do idle_watcher.start |idle_watcher, status| { + do idle_watcher.start |mut idle_watcher, status| { assert!(status.is_none()); - let mut idle_watcher = idle_watcher; idle_watcher.stop(); idle_watcher.close(||()); f(); @@ -81,6 +80,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) } @@ -100,6 +103,89 @@ fn test_callback_run_once() { } } +pub struct UvRemoteCallback { + // The uv async handle for triggering the callback + async: AsyncWatcher, + // 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 = exclusive(false); + let exit_flag_clone = exit_flag.clone(); + let async = do AsyncWatcher::new(loop_) |watcher, status| { + assert!(status.is_none()); + f(); + unsafe { + do exit_flag_clone.with_imm |&should_exit| { + if should_exit { + 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 drop(&self) { + unsafe { + let this: &mut UvRemoteCallback = cast::transmute_mut(self); + 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(); + } + } + } +} + +#[cfg(test)] +mod test_remote { + 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::new_empty(); + do Local::borrow::() |sched| { + let tube_clone = tube_clone.clone(); + let tube_clone_cell = Cell::new(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); impl UvIoFactory { @@ -122,12 +208,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::new(task); @@ -136,7 +220,7 @@ impl IoFactory for UvIoFactory { rtdebug!("connect: in connect callback"); if status.is_none() { rtdebug!("status is none"); - let res = Ok(~UvTcpStream { watcher: stream_watcher }); + let res = Ok(~UvTcpStream(stream_watcher)); // Store the stream in the task's stack unsafe { (*result_cell_ptr).put_back(res); } @@ -167,7 +251,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::new(task); do watcher.as_stream().close { let scheduler = Local::take::(); @@ -178,6 +262,24 @@ impl IoFactory for UvIoFactory { } } } + + fn udp_bind(&mut self, addr: IpAddr) -> Result<~RtioUdpSocketObject, IoError> { + let /*mut*/ watcher = UdpWatcher::new(self.uv_loop()); + match watcher.bind(addr) { + Ok(_) => Ok(~UvUdpSocket(watcher)), + Err(uverr) => { + let scheduler = Local::take::(); + do scheduler.deschedule_running_task_and_then |_, task| { + let task_cell = Cell::new(task); + do watcher.close { + let scheduler = Local::take::(); + scheduler.resume_task_immediately(task_cell.take()); + } + } + Err(uv_error_to_io_error(uverr)) + } + } + } } // FIXME #6090: Prefer newtype structs but Drop doesn't work @@ -203,7 +305,7 @@ impl Drop for UvTcpListener { fn drop(&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::new(task); do watcher.as_stream().close { let scheduler = Local::take::(); @@ -213,6 +315,11 @@ impl Drop for UvTcpListener { } } +impl RtioSocket for UvTcpListener { + // XXX implement + fn socket_name(&self) -> IpAddr { fail!(); } +} + impl RtioTcpListener for UvTcpListener { fn accept(&mut self) -> Result<~RtioTcpStreamObject, IoError> { @@ -229,15 +336,14 @@ impl RtioTcpListener for UvTcpListener { let incoming_streams_cell = Cell::new(incoming_streams_cell.take()); let mut server_tcp_watcher = server_tcp_watcher; - do server_tcp_watcher.listen |server_stream_watcher, status| { + do server_tcp_watcher.listen |mut server_stream_watcher, status| { let maybe_stream = if status.is_none() { - let mut server_stream_watcher = server_stream_watcher; let mut loop_ = server_stream_watcher.event_loop(); let client_tcp_watcher = TcpWatcher::new(&mut loop_); let client_tcp_watcher = client_tcp_watcher.as_stream(); // XXX: Need's to be surfaced in interface server_stream_watcher.accept(client_tcp_watcher); - Ok(~UvTcpStream { watcher: client_tcp_watcher }) + Ok(~UvTcpStream(client_tcp_watcher)) } else { Err(standard_error(OtherIoError)) }; @@ -249,25 +355,22 @@ impl RtioTcpListener for UvTcpListener { return self.incoming_streams.recv(); } -} -// FIXME #6090: Prefer newtype structs but Drop doesn't work -pub struct UvTcpStream { - watcher: StreamWatcher + // XXX implement + fn accept_simultaneously(&self) { fail!(); } + fn dont_accept_simultaneously(&self) { fail!(); } } -impl UvTcpStream { - fn watcher(&self) -> StreamWatcher { self.watcher } -} +// FIXME #6090: Prefer newtype structs but Drop doesn't work +pub struct UvTcpStream(StreamWatcher); impl Drop for UvTcpStream { fn drop(&self) { 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::new(task); - do watcher.close { + do self.close { let scheduler = Local::take::(); scheduler.resume_task_immediately(task_cell.take()); } @@ -275,34 +378,35 @@ impl Drop for UvTcpStream { } } +impl RtioSocket for UvTcpStream { + // XXX implement + fn socket_name(&self) -> IpAddr { fail!(); } +} + impl RtioTcpStream for UvTcpStream { - fn read(&mut self, buf: &mut [u8]) -> Result { + fn read(&self, buf: &mut [u8]) -> Result { let result_cell = Cell::new_empty(); let result_cell_ptr: *Cell> = &result_cell; let scheduler = Local::take::(); 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()); - } - let mut watcher = watcher; + assert!(!sched.in_task_context()); let task_cell = Cell::new(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| { + let mut watcher = **self; + do watcher.read_start(alloc) |mut 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() { @@ -323,17 +427,16 @@ impl RtioTcpStream for UvTcpStream { return result_cell.take(); } - fn write(&mut self, buf: &[u8]) -> Result<(), IoError> { + fn write(&self, buf: &[u8]) -> Result<(), IoError> { let result_cell = Cell::new_empty(); let result_cell_ptr: *Cell> = &result_cell; let scheduler = Local::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; + do scheduler.deschedule_running_task_and_then |_, task| { let task_cell = Cell::new(task); let buf = unsafe { slice_to_uv_buf(*buf_ptr) }; + let mut watcher = **self; do watcher.write(buf) |_watcher, status| { let result = if status.is_none() { Ok(()) @@ -351,6 +454,112 @@ impl RtioTcpStream for UvTcpStream { assert!(!result_cell.is_empty()); return result_cell.take(); } + + // XXX implement + fn peer_name(&self) -> IpAddr { fail!(); } + fn control_congestion(&self) { fail!(); } + fn nodelay(&self) { fail!(); } + fn keepalive(&self, _delay_in_seconds: uint) { fail!(); } + fn letdie(&self) { fail!(); } +} + +pub struct UvUdpSocket(UdpWatcher); + +impl Drop for UvUdpSocket { + fn drop(&self) { + rtdebug!("closing udp socket"); + let scheduler = Local::take::(); + do scheduler.deschedule_running_task_and_then |_, task| { + let task_cell = Cell::new(task); + do self.close { + let scheduler = Local::take::(); + scheduler.resume_task_immediately(task_cell.take()); + } + } + } +} + +impl RtioSocket for UvUdpSocket { + // XXX implement + fn socket_name(&self) -> IpAddr { fail!(); } +} + +impl RtioUdpSocket for UvUdpSocket { + fn recvfrom(&self, buf: &mut [u8]) -> Result<(uint, IpAddr), IoError> { + let result_cell = Cell::new_empty(); + let result_cell_ptr: *Cell> = &result_cell; + + let scheduler = Local::take::(); + assert!(scheduler.in_task_context()); + let buf_ptr: *&mut [u8] = &buf; + do scheduler.deschedule_running_task_and_then |sched, task| { + rtdebug!("recvfrom: entered scheduler context"); + assert!(!sched.in_task_context()); + let task_cell = Cell::new(task); + let alloc: AllocCallback = |_| unsafe { slice_to_uv_buf(*buf_ptr) }; + do self.recv_start(alloc) |watcher, nread, _buf, addr, flags, status| { + let _ = flags; // XXX add handling for partials? + + watcher.recv_stop(); + + let result = match status { + None => { + assert!(nread >= 0); + Ok((nread as uint, addr)) + } + Some(err) => Err(uv_error_to_io_error(err)) + }; + + unsafe { (*result_cell_ptr).put_back(result); } + + let scheduler = Local::take::(); + scheduler.resume_task_immediately(task_cell.take()); + } + } + + assert!(!result_cell.is_empty()); + return result_cell.take(); + } + + fn sendto(&self, buf: &[u8], dst: IpAddr) -> Result<(), IoError> { + let result_cell = Cell::new_empty(); + let result_cell_ptr: *Cell> = &result_cell; + let scheduler = Local::take::(); + assert!(scheduler.in_task_context()); + let buf_ptr: *&[u8] = &buf; + do scheduler.deschedule_running_task_and_then |_, task| { + let task_cell = Cell::new(task); + let buf = unsafe { slice_to_uv_buf(*buf_ptr) }; + do self.send(buf, dst) |_watcher, status| { + + let result = match status { + None => Ok(()), + Some(err) => Err(uv_error_to_io_error(err)), + }; + + unsafe { (*result_cell_ptr).put_back(result); } + + let scheduler = Local::take::(); + scheduler.resume_task_immediately(task_cell.take()); + } + } + + assert!(!result_cell.is_empty()); + return result_cell.take(); + } + + // XXX implement + fn join_multicast(&self, _multi: IpAddr) { fail!(); } + fn leave_multicast(&self, _multi: IpAddr) { fail!(); } + + fn loop_multicast_locally(&self) { fail!(); } + fn dont_loop_multicast_locally(&self) { fail!(); } + + fn multicast_time_to_live(&self, _ttl: int) { fail!(); } + fn time_to_live(&self, _ttl: int) { fail!(); } + + fn hear_broadcasts(&self) { fail!(); } + fn ignore_broadcasts(&self) { fail!(); } } #[test] @@ -365,6 +574,18 @@ fn test_simple_io_no_connect() { } } +#[test] +fn test_simple_udp_io_bind_only() { + do run_in_newsched_task { + unsafe { + let io = Local::unsafe_borrow::(); + let addr = next_test_ip4(); + let maybe_socket = (*io).udp_bind(addr); + assert!(maybe_socket.is_ok()); + } + } +} + #[test] fn test_simple_tcp_server_and_client() { do run_in_newsched_task { @@ -375,7 +596,7 @@ fn test_simple_tcp_server_and_client() { unsafe { let io = Local::unsafe_borrow::(); let mut listener = (*io).tcp_bind(addr).unwrap(); - let mut stream = listener.accept().unwrap(); + let stream = listener.accept().unwrap(); let mut buf = [0, .. 2048]; let nread = stream.read(buf).unwrap(); assert_eq!(nread, 8); @@ -389,13 +610,44 @@ fn test_simple_tcp_server_and_client() { do spawntask_immediately { unsafe { let io = Local::unsafe_borrow::(); - let mut stream = (*io).tcp_connect(addr).unwrap(); + let stream = (*io).tcp_connect(addr).unwrap(); stream.write([0, 1, 2, 3, 4, 5, 6, 7]); } } } } +#[test] +fn test_simple_udp_server_and_client() { + do run_in_newsched_task { + let server_addr = next_test_ip4(); + let client_addr = next_test_ip4(); + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let server_socket = (*io).udp_bind(server_addr).unwrap(); + let mut buf = [0, .. 2048]; + let (nread,src) = server_socket.recvfrom(buf).unwrap(); + assert_eq!(nread, 8); + for uint::range(0, nread) |i| { + rtdebug!("%u", buf[i] as uint); + assert_eq!(buf[i], i as u8); + } + assert_eq!(src, client_addr); + } + } + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let client_socket = (*io).udp_bind(client_addr).unwrap(); + client_socket.sendto([0, 1, 2, 3, 4, 5, 6, 7], server_addr); + } + } + } +} + #[test] #[ignore(reason = "busted")] fn test_read_and_block() { do run_in_newsched_task { @@ -404,7 +656,7 @@ fn test_read_and_block() { do spawntask_immediately { let io = unsafe { Local::unsafe_borrow::() }; let mut listener = unsafe { (*io).tcp_bind(addr).unwrap() }; - let mut stream = listener.accept().unwrap(); + let stream = listener.accept().unwrap(); let mut buf = [0, .. 2048]; let expected = 32; @@ -424,11 +676,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::new(task); - do Local::borrow:: |scheduler| { - scheduler.enqueue_task(task.take()); - } + sched.enqueue_task(task.take()); } } @@ -439,7 +689,7 @@ fn test_read_and_block() { do spawntask_immediately { unsafe { let io = Local::unsafe_borrow::(); - let mut stream = (*io).tcp_connect(addr).unwrap(); + let stream = (*io).tcp_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]); @@ -460,7 +710,7 @@ fn test_read_read_read() { unsafe { let io = Local::unsafe_borrow::(); let mut listener = (*io).tcp_bind(addr).unwrap(); - let mut stream = listener.accept().unwrap(); + let stream = listener.accept().unwrap(); let buf = [1, .. 2048]; let mut total_bytes_written = 0; while total_bytes_written < MAX { @@ -473,7 +723,7 @@ fn test_read_read_read() { do spawntask_immediately { unsafe { let io = Local::unsafe_borrow::(); - let mut stream = (*io).tcp_connect(addr).unwrap(); + let stream = (*io).tcp_connect(addr).unwrap(); let mut buf = [0, .. 2048]; let mut total_bytes_read = 0; while total_bytes_read < MAX { @@ -489,3 +739,96 @@ fn test_read_read_read() { } } } + +#[test] +fn test_udp_twice() { + do run_in_newsched_task { + let server_addr = next_test_ip4(); + let client_addr = next_test_ip4(); + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let client = (*io).udp_bind(client_addr).unwrap(); + assert!(client.sendto([1], server_addr).is_ok()); + assert!(client.sendto([2], server_addr).is_ok()); + } + } + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let server = (*io).udp_bind(server_addr).unwrap(); + let mut buf1 = [0]; + let mut buf2 = [0]; + let (nread1, src1) = server.recvfrom(buf1).unwrap(); + let (nread2, src2) = server.recvfrom(buf2).unwrap(); + assert_eq!(nread1, 1); + assert_eq!(nread2, 1); + assert_eq!(src1, client_addr); + assert_eq!(src2, client_addr); + assert_eq!(buf1[0], 1); + assert_eq!(buf2[0], 2); + } + } + } +} + +#[test] +fn test_udp_many_read() { + do run_in_newsched_task { + let server_out_addr = next_test_ip4(); + let server_in_addr = next_test_ip4(); + let client_out_addr = next_test_ip4(); + let client_in_addr = next_test_ip4(); + static MAX: uint = 500_000; + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let server_out = (*io).udp_bind(server_out_addr).unwrap(); + let server_in = (*io).udp_bind(server_in_addr).unwrap(); + let msg = [1, .. 2048]; + let mut total_bytes_sent = 0; + let mut buf = [1]; + while buf[0] == 1 { + // send more data + assert!(server_out.sendto(msg, client_in_addr).is_ok()); + total_bytes_sent += msg.len(); + // check if the client has received enough + let res = server_in.recvfrom(buf); + assert!(res.is_ok()); + let (nread, src) = res.unwrap(); + assert_eq!(nread, 1); + assert_eq!(src, client_out_addr); + } + assert!(total_bytes_sent >= MAX); + } + } + + do spawntask_immediately { + unsafe { + let io = Local::unsafe_borrow::(); + let client_out = (*io).udp_bind(client_out_addr).unwrap(); + let client_in = (*io).udp_bind(client_in_addr).unwrap(); + let mut total_bytes_recv = 0; + let mut buf = [0, .. 2048]; + while total_bytes_recv < MAX { + // ask for more + assert!(client_out.sendto([1], server_in_addr).is_ok()); + // wait for data + let res = client_in.recvfrom(buf); + assert!(res.is_ok()); + let (nread, src) = res.unwrap(); + assert_eq!(src, server_out_addr); + total_bytes_recv += nread; + for uint::range(0, nread) |i| { + assert_eq!(buf[i], 1); + } + } + // tell the server we're done + assert!(client_out.sendto([0], server_in_addr).is_ok()); + } + } + } +} diff --git a/src/libstd/rt/uv/uvll.rs b/src/libstd/rt/uv/uvll.rs index 6263fd47df674..62bf8f27af93d 100644 --- a/src/libstd/rt/uv/uvll.rs +++ b/src/libstd/rt/uv/uvll.rs @@ -60,17 +60,24 @@ 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_udp_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_udp_send_t = c_void; pub type uv_idle_cb = *u8; +pub type uv_alloc_cb = *u8; +pub type uv_udp_send_cb = *u8; +pub type uv_udp_recv_cb = *u8; +pub type sockaddr = c_void; pub type sockaddr_in = c_void; pub type sockaddr_in6 = c_void; +pub type uv_membership = c_void; #[deriving(Eq)] pub enum uv_handle_type { @@ -187,31 +194,88 @@ pub unsafe fn idle_stop(handle: *uv_idle_t) -> c_int { rust_uv_idle_stop(handle) } +pub unsafe fn udp_init(loop_handle: *uv_loop_t, handle: *uv_udp_t) -> c_int { + return rust_uv_udp_init(loop_handle, handle); +} + +pub unsafe fn udp_bind(server: *uv_udp_t, addr: *sockaddr_in, flags: c_uint) -> c_int { + return rust_uv_udp_bind(server, addr, flags); +} + +pub unsafe fn udp_bind6(server: *uv_udp_t, addr: *sockaddr_in6, flags: c_uint) -> c_int { + return rust_uv_udp_bind6(server, addr, flags); +} + +pub unsafe fn udp_send(req: *uv_udp_send_t, handle: *T, buf_in: &[uv_buf_t], + addr: *sockaddr_in, cb: uv_udp_send_cb) -> c_int { + let buf_ptr = vec::raw::to_ptr(buf_in); + let buf_cnt = buf_in.len() as i32; + return rust_uv_udp_send(req, handle as *c_void, buf_ptr, buf_cnt, addr, cb); +} + +pub unsafe fn udp_send6(req: *uv_udp_send_t, handle: *T, buf_in: &[uv_buf_t], + addr: *sockaddr_in6, cb: uv_udp_send_cb) -> c_int { + let buf_ptr = vec::raw::to_ptr(buf_in); + let buf_cnt = buf_in.len() as i32; + return rust_uv_udp_send6(req, handle as *c_void, buf_ptr, buf_cnt, addr, cb); +} + +pub unsafe fn udp_recv_start(server: *uv_udp_t, on_alloc: uv_alloc_cb, + on_recv: uv_udp_recv_cb) -> c_int { + return rust_uv_udp_recv_start(server, on_alloc, on_recv); +} + +pub unsafe fn udp_recv_stop(server: *uv_udp_t) -> c_int { + return rust_uv_udp_recv_stop(server); +} + +pub unsafe fn get_udp_handle_from_send_req(send_req: *uv_udp_send_t) -> *uv_udp_t { + return rust_uv_get_udp_handle_from_send_req(send_req); +} + +pub unsafe fn udp_get_sockname(handle: *uv_udp_t, name: *sockaddr_in) -> c_int { + return rust_uv_udp_getsockname(handle, name); +} + +pub unsafe fn udp_get_sockname6(handle: *uv_udp_t, name: *sockaddr_in6) -> c_int { + return rust_uv_udp_getsockname6(handle, name); +} + +pub unsafe fn udp_set_membership(handle: *uv_udp_t, multicast_addr: *c_char, + interface_addr: *c_char, membership: uv_membership) -> c_int { + return rust_uv_udp_set_membership(handle, multicast_addr, interface_addr, membership); +} + +pub unsafe fn udp_set_multicast_loop(handle: *uv_udp_t, on: c_int) -> c_int { + return rust_uv_udp_set_multicast_loop(handle, on); +} + +pub unsafe fn udp_set_multicast_ttl(handle: *uv_udp_t, ttl: c_int) -> c_int { + return rust_uv_udp_set_multicast_ttl(handle, ttl); +} + +pub unsafe fn udp_set_broadcast(handle: *uv_udp_t, on: c_int) -> c_int { + return rust_uv_udp_set_broadcast(handle, on); +} + 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); +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); + +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); } @@ -224,6 +288,26 @@ pub unsafe fn tcp_getpeername6(tcp_handle_ptr: *uv_tcp_t, name: *sockaddr_in6) - return rust_uv_tcp_getpeername6(tcp_handle_ptr, name); } +pub unsafe fn tcp_getsockname(handle: *uv_tcp_t, name: *sockaddr_in) -> c_int { + return rust_uv_tcp_getsockname(handle, name); +} + +pub unsafe fn tcp_getsockname6(handle: *uv_tcp_t, name: *sockaddr_in6) -> c_int { + return rust_uv_tcp_getsockname6(handle, name); +} + +pub unsafe fn tcp_nodelay(handle: *uv_tcp_t, enable: c_int) -> c_int { + return rust_uv_tcp_nodelay(handle, enable); +} + +pub unsafe fn tcp_keepalive(handle: *uv_tcp_t, enable: c_int, delay: c_uint) -> c_int { + return rust_uv_tcp_keepalive(handle, enable, delay); +} + +pub unsafe fn tcp_simultaneous_accepts(handle: *uv_tcp_t, enable: c_int) -> c_int { + return rust_uv_tcp_simultaneous_accepts(handle, enable); +} + pub unsafe fn listen(stream: *T, backlog: c_int, cb: *u8) -> c_int { return rust_uv_listen(stream as *c_void, backlog, cb); } @@ -237,7 +321,7 @@ pub unsafe fn write(req: *uv_write_t, stream: *T, buf_in: &[uv_buf_t], cb: *u 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 { +pub unsafe fn read_start(stream: *uv_stream_t, on_alloc: uv_alloc_cb, on_read: *u8) -> c_int { return rust_uv_read_start(stream as *c_void, on_alloc, on_read); } @@ -281,6 +365,22 @@ pub unsafe fn timer_stop(timer_ptr: *uv_timer_t) -> c_int { return rust_uv_timer_stop(timer_ptr); } +pub unsafe fn is_ip4_addr(addr: *sockaddr) -> bool { + match rust_uv_is_ipv4_sockaddr(addr) { 0 => false, _ => true } +} + +pub unsafe fn is_ip6_addr(addr: *sockaddr) -> bool { + match rust_uv_is_ipv6_sockaddr(addr) { 0 => false, _ => true } +} + +pub unsafe fn as_sockaddr_in(addr: *sockaddr) -> *sockaddr_in { + return rust_uv_sockaddr_as_sockaddr_in(addr); +} + +pub unsafe fn as_sockaddr_in6(addr: *sockaddr) -> *sockaddr_in6 { + return rust_uv_sockaddr_as_sockaddr_in6(addr); +} + 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) @@ -300,6 +400,22 @@ pub unsafe fn free_ip6_addr(addr: *sockaddr_in6) { rust_uv_free_ip6_addr(addr); } +pub unsafe fn ip4_name(addr: *sockaddr_in, dst: *u8, size: size_t) -> c_int { + return rust_uv_ip4_name(addr, dst, size); +} + +pub unsafe fn ip6_name(addr: *sockaddr_in6, dst: *u8, size: size_t) -> c_int { + return rust_uv_ip6_name(addr, dst, size); +} + +pub unsafe fn ip4_port(addr: *sockaddr_in) -> c_uint { + return rust_uv_ip4_port(addr); +} + +pub unsafe fn ip6_port(addr: *sockaddr_in6) -> c_uint { + return rust_uv_ip6_port(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); @@ -384,16 +500,11 @@ extern { 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_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; @@ -403,40 +514,51 @@ extern { 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, + fn rust_uv_tcp_connect(req: *uv_connect_t, handle: *uv_tcp_t, 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, + fn rust_uv_tcp_connect6(req: *uv_connect_t, handle: *uv_tcp_t, 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_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_tcp_getsockname(handle: *uv_tcp_t, name: *sockaddr_in) -> c_int; + fn rust_uv_tcp_getsockname6(handle: *uv_tcp_t, name: *sockaddr_in6) -> c_int; + fn rust_uv_tcp_nodelay(handle: *uv_tcp_t, enable: c_int) -> c_int; + fn rust_uv_tcp_keepalive(handle: *uv_tcp_t, enable: c_int, delay: c_uint) -> c_int; + fn rust_uv_tcp_simultaneous_accepts(handle: *uv_tcp_t, enable: c_int) -> c_int; + + fn rust_uv_udp_init(loop_handle: *uv_loop_t, handle_ptr: *uv_udp_t) -> c_int; + fn rust_uv_udp_bind(server: *uv_udp_t, addr: *sockaddr_in, flags: c_uint) -> c_int; + fn rust_uv_udp_bind6(server: *uv_udp_t, addr: *sockaddr_in6, flags: c_uint) -> c_int; + fn rust_uv_udp_send(req: *uv_udp_send_t, handle: *uv_udp_t, buf_in: *uv_buf_t, + buf_cnt: c_int, addr: *sockaddr_in, cb: *u8) -> c_int; + fn rust_uv_udp_send6(req: *uv_udp_send_t, handle: *uv_udp_t, buf_in: *uv_buf_t, + buf_cnt: c_int, addr: *sockaddr_in6, cb: *u8) -> c_int; + fn rust_uv_udp_recv_start(server: *uv_udp_t, on_alloc: *u8, on_recv: *u8) -> c_int; + fn rust_uv_udp_recv_stop(server: *uv_udp_t) -> c_int; + fn rust_uv_get_udp_handle_from_send_req(req: *uv_udp_send_t) -> *uv_udp_t; + fn rust_uv_udp_getsockname(handle: *uv_udp_t, name: *sockaddr_in) -> c_int; + fn rust_uv_udp_getsockname6(handle: *uv_udp_t, name: *sockaddr_in6) -> c_int; + fn rust_uv_udp_set_membership(handle: *uv_udp_t, multicast_addr: *c_char, + interface_addr: *c_char, membership: uv_membership) -> c_int; + fn rust_uv_udp_set_multicast_loop(handle: *uv_udp_t, on: c_int) -> c_int; + fn rust_uv_udp_set_multicast_ttl(handle: *uv_udp_t, ttl: c_int) -> c_int; + fn rust_uv_udp_set_broadcast(handle: *uv_udp_t, on: c_int) -> c_int; + + fn rust_uv_is_ipv4_sockaddr(addr: *sockaddr) -> c_int; + fn rust_uv_is_ipv6_sockaddr(addr: *sockaddr) -> c_int; + fn rust_uv_sockaddr_as_sockaddr_in(addr: *sockaddr) -> *sockaddr_in; + fn rust_uv_sockaddr_as_sockaddr_in6(addr: *sockaddr) -> *sockaddr_in6; + 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, + 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_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: libc::uint64_t, + 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: libc::uint64_t, repeat: libc::uint64_t) -> c_int; fn rust_uv_timer_stop(handle: *uv_timer_t) -> c_int; diff --git a/src/libstd/rt/uvio.rs b/src/libstd/rt/uvio.rs deleted file mode 100644 index 0187ad3abf517..0000000000000 --- a/src/libstd/rt/uvio.rs +++ /dev/null @@ -1,458 +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 cell::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 -} - -impl UvEventLoop { - pub fn new() -> UvEventLoop { - UvEventLoop { - uvio: UvIoFactory(Loop::new()) - } - } - - /// A convenience constructor - pub fn new_scheduler() -> Scheduler { - Scheduler::new(~UvEventLoop::new()) - } -} - -impl Drop for UvEventLoop { - fn drop(&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); - -impl UvIoFactory { - pub 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 = Cell::new_empty(); - 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::new(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 drop(&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 = Cell::new_empty(); - 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::new(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 drop(&self) { - rtdebug!("closing stream"); - //self.watcher().close(||()); - } -} - -impl Stream for UvStream { - fn read(&mut self, buf: &mut [u8]) -> Result { - let result_cell = Cell::new_empty(); - 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::new(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 = Cell::new_empty(); - 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::new(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::new(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 0d298bde6b508..0000000000000 --- 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; -} diff --git a/src/libstd/sys.rs b/src/libstd/sys.rs index a1d6342323c64..9f72f941bde9b 100644 --- a/src/libstd/sys.rs +++ b/src/libstd/sys.rs @@ -12,7 +12,6 @@ #[allow(missing_doc)]; -use option::{Some, None}; use cast; use gc; use io; @@ -151,10 +150,12 @@ 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 option::Option; + use cell::Cell; + use either::Left; use rt::{context, OldTaskContext, TaskContext}; - use rt::task::{Task, Unwinder}; + use rt::task::Task; use rt::local::Local; + use rt::logging::Logger; let context = context(); match context { @@ -171,24 +172,29 @@ 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 at '%s', %s:%i", + msg, file, line as int); // XXX: Logging doesn't work correctly in non-task context because it // invokes the local heap if context == TaskContext { - 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); + rterrln!("%s", outmsg); } 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") + if (*task).unwinder.unwinding { + rtabort!("unwinding again"); } + (*task).unwinder.begin_unwind(); } } } diff --git a/src/libstd/task/mod.rs b/src/libstd/task/mod.rs index a8e8cfd163ad7..5a3ff10ae83e6 100644 --- a/src/libstd/task/mod.rs +++ b/src/libstd/task/mod.rs @@ -497,11 +497,26 @@ pub fn try(f: ~fn() -> T) -> Result { pub fn yield() { //! Yield control to the task scheduler + use rt::{context, OldTaskContext}; + use rt::local::Local; + use rt::sched::Scheduler; + unsafe { - let task_ = rt::rust_get_task(); - let killed = rt::rust_task_yield(task_); - if killed && !failing() { - fail!("killed"); + match context() { + OldTaskContext => { + let task_ = rt::rust_get_task(); + let killed = rt::rust_task_yield(task_); + if killed && !failing() { + fail!("killed"); + } + } + _ => { + // XXX: What does yield really mean in newsched? + let sched = Local::take::(); + do sched.deschedule_running_task_and_then |sched, task| { + sched.enqueue_task(task); + } + } } } } @@ -520,20 +535,9 @@ 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 - } - } + do Local::borrow:: |local| { + local.unwinder.unwinding } - return unwinding; } } } @@ -1191,3 +1195,4 @@ fn test_simple_newsched_spawn() { spawn(||()) } } + diff --git a/src/libstd/task/spawn.rs b/src/libstd/task/spawn.rs index 190485a720aa7..bcb7e06bf1f74 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; use iterator::IteratorUtil; #[cfg(test)] use task::default_task_opts; @@ -581,12 +582,41 @@ pub fn spawn_raw(opts: TaskOpts, f: ~fn()) { } } -fn spawn_raw_newsched(_opts: TaskOpts, f: ~fn()) { +fn spawn_raw_newsched(mut opts: TaskOpts, f: ~fn()) { use rt::sched::*; - let mut sched = Local::take::(); - let task = ~Coroutine::new(&mut sched.stack_pool, f); - sched.schedule_new_task(task); + let f = Cell::new(f); + + let mut task = unsafe { + let sched = Local::unsafe_borrow::(); + rtdebug!("unsafe borrowed sched"); + + if opts.linked { + do Local::borrow::() |running_task| { + ~running_task.new_child(&mut (*sched).stack_pool, f.take()) + } + } else { + // An unlinked task is a new root in the task tree + ~Task::new_root(&mut (*sched).stack_pool, f.take()) + } + }; + + if opts.notify_chan.is_some() { + let notify_chan = opts.notify_chan.swap_unwrap(); + let notify_chan = Cell::new(notify_chan); + let on_exit: ~fn(bool) = |success| { + notify_chan.take().send( + if success { Success } else { Failure } + ) + }; + task.on_exit = Some(on_exit); + } + + rtdebug!("spawn about to take scheduler"); + + let sched = Local::take::(); + rtdebug!("took sched in spawn"); + sched.schedule_task(task); } fn spawn_raw_oldsched(mut opts: TaskOpts, f: ~fn()) { diff --git a/src/libstd/unstable/lang.rs b/src/libstd/unstable/lang.rs index 94617b4e49f04..66bb46b8991a5 100644 --- a/src/libstd/unstable/lang.rs +++ b/src/libstd/unstable/lang.rs @@ -10,29 +10,21 @@ //! Runtime calls emitted by the compiler. -use iterator::IteratorUtil; -use uint; use cast::transmute; -use libc::{c_char, c_uchar, c_void, size_t, uintptr_t, c_int, STDERR_FILENO}; -use managed::raw::BoxRepr; +use libc::{c_char, c_uchar, c_void, size_t, uintptr_t, c_int}; use str; use sys; use rt::{context, OldTaskContext}; use rt::task::Task; use rt::local::Local; -use option::{Option, Some, None}; -use io; +use rt::borrowck; #[allow(non_camel_case_types)] pub type rust_task = c_void; -pub static FROZEN_BIT: uint = 1 << (uint::bits - 1); -pub static MUT_BIT: uint = 1 << (uint::bits - 2); -static ALL_BITS: uint = FROZEN_BIT | MUT_BIT; - pub mod rustrt { use unstable::lang::rust_task; - use libc::{c_void, c_char, uintptr_t}; + use libc::{c_char, uintptr_t}; pub extern { #[rust_stack] @@ -46,15 +38,6 @@ pub mod rustrt { size: uintptr_t) -> *c_char; - #[fast_ffi] - unsafe fn rust_upcall_free_noswitch(ptr: *c_char); - - #[rust_stack] - fn rust_take_task_borrow_list(task: *rust_task) -> *c_void; - - #[rust_stack] - fn rust_set_task_borrow_list(task: *rust_task, map: *c_void); - #[rust_stack] fn rust_try_get_task() -> *rust_task; @@ -77,149 +60,6 @@ pub fn fail_bounds_check(file: *c_char, line: size_t, } } -#[deriving(Eq)] -struct BorrowRecord { - box: *mut BoxRepr, - file: *c_char, - line: size_t -} - -fn try_take_task_borrow_list() -> Option<~[BorrowRecord]> { - unsafe { - let cur_task: *rust_task = rustrt::rust_try_get_task(); - if cur_task.is_not_null() { - let ptr = rustrt::rust_take_task_borrow_list(cur_task); - if ptr.is_null() { - None - } else { - let v: ~[BorrowRecord] = transmute(ptr); - Some(v) - } - } else { - None - } - } -} - -fn swap_task_borrow_list(f: &fn(~[BorrowRecord]) -> ~[BorrowRecord]) { - unsafe { - let cur_task: *rust_task = rustrt::rust_try_get_task(); - if cur_task.is_not_null() { - let mut borrow_list: ~[BorrowRecord] = { - let ptr = rustrt::rust_take_task_borrow_list(cur_task); - if ptr.is_null() { ~[] } else { transmute(ptr) } - }; - borrow_list = f(borrow_list); - rustrt::rust_set_task_borrow_list(cur_task, transmute(borrow_list)); - } - } -} - -pub unsafe fn clear_task_borrow_list() { - // pub because it is used by the box annihilator. - let _ = try_take_task_borrow_list(); -} - -unsafe fn fail_borrowed(box: *mut BoxRepr, file: *c_char, line: size_t) { - debug_borrow("fail_borrowed: ", box, 0, 0, file, line); - - match try_take_task_borrow_list() { - None => { // not recording borrows - let msg = "borrowed"; - do str::as_buf(msg) |msg_p, _| { - fail_(msg_p as *c_char, file, line); - } - } - Some(borrow_list) => { // recording borrows - let mut msg = ~"borrowed"; - let mut sep = " at "; - for borrow_list.rev_iter().advance |entry| { - if entry.box == box { - msg.push_str(sep); - let filename = str::raw::from_c_str(entry.file); - msg.push_str(filename); - msg.push_str(fmt!(":%u", entry.line as uint)); - sep = " and at "; - } - } - do str::as_buf(msg) |msg_p, _| { - fail_(msg_p as *c_char, file, line) - } - } - } -} - -/// Because this code is so perf. sensitive, use a static constant so that -/// debug printouts are compiled out most of the time. -static ENABLE_DEBUG: bool = false; - -#[inline] -unsafe fn debug_borrow(tag: &'static str, - p: *const T, - old_bits: uint, - new_bits: uint, - filename: *c_char, - line: size_t) { - //! A useful debugging function that prints a pointer + tag + newline - //! without allocating memory. - - if ENABLE_DEBUG && ::rt::env::get().debug_borrow { - debug_borrow_slow(tag, p, old_bits, new_bits, filename, line); - } - - unsafe fn debug_borrow_slow(tag: &'static str, - p: *const T, - old_bits: uint, - new_bits: uint, - filename: *c_char, - line: size_t) { - let dbg = STDERR_FILENO as io::fd_t; - dbg.write_str(tag); - dbg.write_hex(p as uint); - dbg.write_str(" "); - dbg.write_hex(old_bits); - dbg.write_str(" "); - dbg.write_hex(new_bits); - dbg.write_str(" "); - dbg.write_cstr(filename); - dbg.write_str(":"); - dbg.write_hex(line as uint); - dbg.write_str("\n"); - } -} - -trait DebugPrints { - fn write_hex(&self, val: uint); - unsafe fn write_cstr(&self, str: *c_char); -} - -impl DebugPrints for io::fd_t { - fn write_hex(&self, mut i: uint) { - let letters = ['0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', 'a', 'b', 'c', 'd', 'e', 'f']; - static UINT_NIBBLES: uint = ::uint::bytes << 1; - let mut buffer = [0_u8, ..UINT_NIBBLES+1]; - let mut c = UINT_NIBBLES; - while c > 0 { - c -= 1; - buffer[c] = letters[i & 0xF] as u8; - i >>= 4; - } - self.write(buffer.slice(0, UINT_NIBBLES)); - } - - unsafe fn write_cstr(&self, p: *c_char) { - use libc::strlen; - use vec; - - let len = strlen(p); - let p: *u8 = transmute(p); - do vec::raw::buf_as_slice(p, len as uint) |s| { - self.write(s); - } - } -} - #[lang="malloc"] pub unsafe fn local_malloc(td: *c_char, size: uintptr_t) -> *c_char { match context() { @@ -228,7 +68,10 @@ 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| { + rtdebug!("task pointer: %x, heap pointer: %x", + to_uint(task), + to_uint(&task.heap)); alloc = task.heap.alloc(td as *c_void, size as uint) as *c_char; } return alloc; @@ -241,110 +84,38 @@ pub unsafe fn local_malloc(td: *c_char, size: uintptr_t) -> *c_char { // problem occurs, call exit instead. #[lang="free"] pub unsafe fn local_free(ptr: *c_char) { - match context() { - OldTaskContext => { - rustrt::rust_upcall_free_noswitch(ptr); - } - _ => { - do Local::borrow:: |task| { - task.heap.free(ptr as *c_void); - } - } - } + ::rt::local_heap::local_free(ptr); } #[lang="borrow_as_imm"] #[inline] pub unsafe fn borrow_as_imm(a: *u8, file: *c_char, line: size_t) -> uint { - let a: *mut BoxRepr = transmute(a); - let old_ref_count = (*a).header.ref_count; - let new_ref_count = old_ref_count | FROZEN_BIT; - - debug_borrow("borrow_as_imm:", a, old_ref_count, new_ref_count, file, line); - - if (old_ref_count & MUT_BIT) != 0 { - fail_borrowed(a, file, line); - } - - (*a).header.ref_count = new_ref_count; - - old_ref_count + borrowck::borrow_as_imm(a, file, line) } #[lang="borrow_as_mut"] #[inline] pub unsafe fn borrow_as_mut(a: *u8, file: *c_char, line: size_t) -> uint { - let a: *mut BoxRepr = transmute(a); - let old_ref_count = (*a).header.ref_count; - let new_ref_count = old_ref_count | MUT_BIT | FROZEN_BIT; - - debug_borrow("borrow_as_mut:", a, old_ref_count, new_ref_count, file, line); - - if (old_ref_count & (MUT_BIT|FROZEN_BIT)) != 0 { - fail_borrowed(a, file, line); - } - - (*a).header.ref_count = new_ref_count; - - old_ref_count + borrowck::borrow_as_mut(a, file, line) } - #[lang="record_borrow"] pub unsafe fn record_borrow(a: *u8, old_ref_count: uint, file: *c_char, line: size_t) { - if (old_ref_count & ALL_BITS) == 0 { - // was not borrowed before - let a: *mut BoxRepr = transmute(a); - debug_borrow("record_borrow:", a, old_ref_count, 0, file, line); - do swap_task_borrow_list |borrow_list| { - let mut borrow_list = borrow_list; - borrow_list.push(BorrowRecord {box: a, file: file, line: line}); - borrow_list - } - } + borrowck::record_borrow(a, old_ref_count, file, line) } #[lang="unrecord_borrow"] pub unsafe fn unrecord_borrow(a: *u8, old_ref_count: uint, file: *c_char, line: size_t) { - if (old_ref_count & ALL_BITS) == 0 { - // was not borrowed before, so we should find the record at - // the end of the list - let a: *mut BoxRepr = transmute(a); - debug_borrow("unrecord_borrow:", a, old_ref_count, 0, file, line); - do swap_task_borrow_list |borrow_list| { - let mut borrow_list = borrow_list; - assert!(!borrow_list.is_empty()); - let br = borrow_list.pop(); - if br.box != a || br.file != file || br.line != line { - let err = fmt!("wrong borrow found, br=%?", br); - do str::as_buf(err) |msg_p, _| { - fail_(msg_p as *c_char, file, line) - } - } - borrow_list - } - } + borrowck::unrecord_borrow(a, old_ref_count, file, line) } #[lang="return_to_mut"] #[inline] pub unsafe fn return_to_mut(a: *u8, orig_ref_count: uint, file: *c_char, line: size_t) { - // Sometimes the box is null, if it is conditionally frozen. - // See e.g. #4904. - if !a.is_null() { - let a: *mut BoxRepr = transmute(a); - let old_ref_count = (*a).header.ref_count; - let new_ref_count = - (old_ref_count & !ALL_BITS) | (orig_ref_count & ALL_BITS); - - debug_borrow("return_to_mut:", - a, old_ref_count, new_ref_count, file, line); - - (*a).header.ref_count = new_ref_count; - } + borrowck::return_to_mut(a, orig_ref_count, file, line) } #[lang="check_not_borrowed"] @@ -352,12 +123,7 @@ pub unsafe fn return_to_mut(a: *u8, orig_ref_count: uint, pub unsafe fn check_not_borrowed(a: *u8, file: *c_char, line: size_t) { - let a: *mut BoxRepr = transmute(a); - let ref_count = (*a).header.ref_count; - debug_borrow("check_not_borrowed:", a, ref_count, 0, file, line); - if (ref_count & FROZEN_BIT) != 0 { - fail_borrowed(a, file, line); - } + borrowck::check_not_borrowed(a, file, line) } #[lang="strdup_uniq"] @@ -366,6 +132,11 @@ pub unsafe fn strdup_uniq(ptr: *c_uchar, len: uint) -> ~str { str::raw::from_buf_len(ptr, len) } +#[lang="annihilate"] +pub unsafe fn annihilate() { + ::cleanup::annihilate() +} + #[lang="start"] pub fn start(main: *u8, argc: int, argv: **c_char, crate_map: *u8) -> int { diff --git a/src/libstd/unstable/sync.rs b/src/libstd/unstable/sync.rs index 06c3ecb814757..0da05dd167d2d 100644 --- a/src/libstd/unstable/sync.rs +++ b/src/libstd/unstable/sync.rs @@ -205,8 +205,53 @@ extern { fn rust_unlock_little_lock(lock: rust_little_lock); } +/* *********************************************************************/ + +//FIXME: #5042 This should be replaced by proper atomic type +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 } + } + pub fn store(&mut self, val: uint) { + unsafe { intrinsics::atomic_store(cast::transmute(self), val as int); } + } + pub fn add(&mut self, val: int) -> uint { + unsafe { intrinsics::atomic_xadd(cast::transmute(self), val as int) as 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; @@ -262,4 +307,28 @@ mod tests { } } } + + #[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); + } } diff --git a/src/libstd/vec.rs b/src/libstd/vec.rs index 33857556548fa..1188cfbe59507 100644 --- a/src/libstd/vec.rs +++ b/src/libstd/vec.rs @@ -20,7 +20,6 @@ use cmp::{Eq, TotalEq, TotalOrd, Ordering, Less, Equal, Greater}; use clone::Clone; use iterator::{FromIterator, Iterator, IteratorUtil}; use kinds::Copy; -use libc; use libc::c_void; use num::Zero; use option::{None, Option, Some}; @@ -33,17 +32,12 @@ use sys::size_of; use uint; use unstable::intrinsics; #[cfg(stage0)] -use intrinsic::{get_tydesc, TyDesc}; +use intrinsic::{get_tydesc}; #[cfg(not(stage0))] -use unstable::intrinsics::{get_tydesc, contains_managed, TyDesc}; +use unstable::intrinsics::{get_tydesc, contains_managed}; use vec; use util; -extern { - #[fast_ffi] - unsafe fn vec_reserve_shared_actual(t: *TyDesc, v: **raw::VecRepr, n: libc::size_t); -} - /// Returns true if two vectors have the same length pub fn same_length(xs: &[T], ys: &[U]) -> bool { xs.len() == ys.len() @@ -1139,7 +1133,9 @@ impl OwnedVector for ~[T] { let td = get_tydesc::(); if ((**ptr).box_header.ref_count == managed::raw::RC_MANAGED_UNIQUE) { - vec_reserve_shared_actual(td, ptr as **raw::VecRepr, n as libc::size_t); + // XXX transmute shouldn't be necessary + let td = cast::transmute(td); + ::at_vec::raw::reserve_raw(td, ptr, n); } else { let alloc = n * sys::nonzero_size_of::(); *ptr = realloc_raw(*ptr as *mut c_void, alloc + size_of::()) @@ -1169,7 +1165,7 @@ impl OwnedVector for ~[T] { let ptr: *mut *mut raw::VecRepr = cast::transmute(self); let td = get_tydesc::(); if contains_managed::() { - vec_reserve_shared_actual(td, ptr as **raw::VecRepr, n as libc::size_t); + ::at_vec::raw::reserve_raw(td, ptr, n); } else { let alloc = n * sys::nonzero_size_of::(); *ptr = realloc_raw(*ptr as *mut c_void, alloc + size_of::()) diff --git a/src/rt/rust_builtin.cpp b/src/rt/rust_builtin.cpp index 17f36e810cd1a..4a5fcf3c60432 100644 --- a/src/rt/rust_builtin.cpp +++ b/src/rt/rust_builtin.cpp @@ -68,11 +68,10 @@ rust_env_pairs() { } #endif -extern "C" CDECL void -vec_reserve_shared_actual(type_desc* ty, rust_vec_box** vp, - size_t n_elts) { +extern "C" CDECL void * +rust_local_realloc(rust_opaque_box *ptr, size_t size) { rust_task *task = rust_get_current_task(); - reserve_vec_exact_shared(task, vp, n_elts * ty->size); + return task->boxed.realloc(ptr, size); } extern "C" CDECL size_t @@ -87,15 +86,10 @@ rand_gen_seed(uint8_t* dest, size_t size) { extern "C" CDECL void * rand_new_seeded(uint8_t* seed, size_t seed_size) { - rust_task *task = rust_get_current_task(); - rust_rng *rng = (rust_rng *) task->malloc(sizeof(rust_rng), - "rand_new_seeded"); - if (!rng) { - task->fail(); - return NULL; - } - char *env_seed = task->kernel->env->rust_seed; - rng_init(rng, env_seed, seed, seed_size); + assert(seed != NULL); + rust_rng *rng = (rust_rng *) malloc(sizeof(rust_rng)); + assert(rng != NULL && "rng alloc failed"); + rng_init(rng, NULL, seed, seed_size); return rng; } @@ -106,8 +100,7 @@ rand_next(rust_rng *rng) { extern "C" CDECL void rand_free(rust_rng *rng) { - rust_task *task = rust_get_current_task(); - task->free(rng); + free(rng); } @@ -594,12 +587,18 @@ rust_log_console_on() { log_console_on(); } -extern void log_console_off(rust_env *env); +extern void log_console_off(); extern "C" CDECL void rust_log_console_off() { - rust_task *task = rust_get_current_task(); - log_console_off(task->kernel->env); + log_console_off(); +} + +extern bool should_log_console(); + +extern "C" CDECL uintptr_t +rust_should_log_console() { + return (uintptr_t)should_log_console(); } extern "C" CDECL void @@ -871,6 +870,12 @@ rust_delete_memory_region(memory_region *region) { delete region; } +extern "C" CDECL boxed_region* +rust_current_boxed_region() { + rust_task *task = rust_get_current_task(); + return &task->boxed; +} + extern "C" CDECL boxed_region* rust_new_boxed_region(memory_region *region, uintptr_t poison_on_free) { @@ -887,6 +892,11 @@ rust_boxed_region_malloc(boxed_region *region, type_desc *td, size_t size) { return region->malloc(td, size); } +extern "C" CDECL rust_opaque_box* +rust_boxed_region_realloc(boxed_region *region, rust_opaque_box *ptr, size_t size) { + return region->realloc(ptr, size); +} + extern "C" CDECL void rust_boxed_region_free(boxed_region *region, rust_opaque_box *box) { region->free(box); @@ -919,6 +929,46 @@ rust_running_on_valgrind() { return RUNNING_ON_VALGRIND; } +extern int get_num_cpus(); + +extern "C" CDECL uintptr_t +rust_get_num_cpus() { + return get_num_cpus(); +} + +static lock_and_signal global_args_lock; +static uintptr_t global_args_ptr = 0; + +extern "C" CDECL void +rust_take_global_args_lock() { + global_args_lock.lock(); +} + +extern "C" CDECL void +rust_drop_global_args_lock() { + global_args_lock.unlock(); +} + +extern "C" CDECL uintptr_t* +rust_get_global_args_ptr() { + return &global_args_ptr; +} + +static lock_and_signal exit_status_lock; +static uintptr_t exit_status = 0; + +extern "C" CDECL void +rust_set_exit_status_newrt(uintptr_t code) { + scoped_lock with(exit_status_lock); + exit_status = code; +} + +extern "C" CDECL uintptr_t +rust_get_exit_status_newrt() { + scoped_lock with(exit_status_lock); + return exit_status; +} + // // Local Variables: // mode: C++ diff --git a/src/rt/rust_env.cpp b/src/rt/rust_env.cpp index eaccb3b0a4424..ff03ea817b868 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/rust_gc_metadata.cpp b/src/rt/rust_gc_metadata.cpp index fbf0575b31dcc..e37856255a7d6 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/rust_log.cpp b/src/rt/rust_log.cpp index df24f569495b4..8179c53e96d5e 100644 --- a/src/rt/rust_log.cpp +++ b/src/rt/rust_log.cpp @@ -43,11 +43,15 @@ log_console_on() { * overridden by the environment. */ void -log_console_off(rust_env *env) { +log_console_off() { scoped_lock with(_log_lock); - if (env->logspec == NULL) { - _log_to_console = false; - } + _log_to_console = false; +} + +bool +should_log_console() { + scoped_lock with(_log_lock); + return _log_to_console; } rust_log::rust_log(rust_sched_loop *sched_loop) : diff --git a/src/rt/rust_test_helpers.cpp b/src/rt/rust_test_helpers.cpp index d82c39d6838ec..2cfd5cf1eb63c 100644 --- a/src/rt/rust_test_helpers.cpp +++ b/src/rt/rust_test_helpers.cpp @@ -168,11 +168,11 @@ rust_dbg_extern_identity_TwoDoubles(TwoDoubles u) { // Generates increasing port numbers for network testing extern "C" CDECL uintptr_t -rust_dbg_next_port() { +rust_dbg_next_port(uintptr_t base_port) { static lock_and_signal dbg_port_lock; - static uintptr_t next_port = 9600; + static uintptr_t next_offset = 0; scoped_lock with(dbg_port_lock); - uintptr_t this_port = next_port; - next_port += 1; + uintptr_t this_port = base_port + next_offset; + next_offset += 1; return this_port; } diff --git a/src/rt/rust_util.h b/src/rt/rust_util.h index 5b8378b0ad35f..242c2ef0a81ad 100644 --- a/src/rt/rust_util.h +++ b/src/rt/rust_util.h @@ -57,16 +57,6 @@ vec_data(rust_vec *v) { return reinterpret_cast(v->data); } -inline void reserve_vec_exact_shared(rust_task* task, rust_vec_box** vpp, - size_t size) { - rust_opaque_box** ovpp = (rust_opaque_box**)vpp; - if (size > (*vpp)->body.alloc) { - *vpp = (rust_vec_box*)task->boxed.realloc( - *ovpp, size + sizeof(rust_vec)); - (*vpp)->body.alloc = size; - } -} - inline void reserve_vec_exact(rust_vec_box** vpp, size_t size) { if (size > (*vpp)->body.alloc) { diff --git a/src/rt/rust_uv.cpp b/src/rt/rust_uv.cpp index dbd52e088f0fd..95e38a9903c4b 100644 --- a/src/rt/rust_uv.cpp +++ b/src/rt/rust_uv.cpp @@ -294,6 +294,118 @@ rust_uv_tcp_getpeername6 return uv_tcp_getpeername(handle, (sockaddr*)name, &namelen); } +extern "C" int +rust_uv_tcp_getsockname +(uv_tcp_t* handle, sockaddr_in* name) { + int namelen = sizeof(sockaddr_in); + return uv_tcp_getsockname(handle, (sockaddr*)name, &namelen); +} + +extern "C" int +rust_uv_tcp_getsockname6 +(uv_tcp_t* handle, sockaddr_in6* name) { + int namelen = sizeof(sockaddr_in6); + return uv_tcp_getsockname(handle, (sockaddr*)name, &namelen); +} + +extern "C" int +rust_uv_tcp_nodelay +(uv_tcp_t* handle, int enable) { + return uv_tcp_nodelay(handle, enable); +} + +extern "C" int +rust_uv_tcp_keepalive +(uv_tcp_t* handle, int enable, unsigned int delay) { + return uv_tcp_keepalive(handle, enable, delay); +} + +extern "C" int +rust_uv_tcp_simultaneous_accepts +(uv_tcp_t* handle, int enable) { + return uv_tcp_simultaneous_accepts(handle, enable); +} + +extern "C" int +rust_uv_udp_init(uv_loop_t* loop, uv_udp_t* handle) { + return uv_udp_init(loop, handle); +} + +extern "C" int +rust_uv_udp_bind(uv_udp_t* server, sockaddr_in* addr_ptr, unsigned flags) { + return uv_udp_bind(server, *addr_ptr, flags); +} + +extern "C" int +rust_uv_udp_bind6(uv_udp_t* server, sockaddr_in6* addr_ptr, unsigned flags) { + return uv_udp_bind6(server, *addr_ptr, flags); +} + +extern "C" int +rust_uv_udp_send(uv_udp_send_t* req, uv_udp_t* handle, uv_buf_t* buf_in, + int buf_cnt, sockaddr_in* addr_ptr, uv_udp_send_cb cb) { + return uv_udp_send(req, handle, buf_in, buf_cnt, *addr_ptr, cb); +} + +extern "C" int +rust_uv_udp_send6(uv_udp_send_t* req, uv_udp_t* handle, uv_buf_t* buf_in, + int buf_cnt, sockaddr_in6* addr_ptr, uv_udp_send_cb cb) { + return uv_udp_send6(req, handle, buf_in, buf_cnt, *addr_ptr, cb); +} + +extern "C" int +rust_uv_udp_recv_start(uv_udp_t* server, uv_alloc_cb on_alloc, uv_udp_recv_cb on_read) { + return uv_udp_recv_start(server, on_alloc, on_read); +} + +extern "C" int +rust_uv_udp_recv_stop(uv_udp_t* server) { + return uv_udp_recv_stop(server); +} + +extern "C" uv_udp_t* +rust_uv_get_udp_handle_from_send_req(uv_udp_send_t* send_req) { + return send_req->handle; +} + +extern "C" int +rust_uv_udp_getsockname +(uv_udp_t* handle, sockaddr_in* name) { + int namelen = sizeof(sockaddr_in); + return uv_udp_getsockname(handle, (sockaddr*)name, &namelen); +} + +extern "C" int +rust_uv_udp_getsockname6 +(uv_udp_t* handle, sockaddr_in6* name) { + int namelen = sizeof(sockaddr_in6); + return uv_udp_getsockname(handle, (sockaddr*)name, &namelen); +} + +extern "C" int +rust_uv_udp_set_membership +(uv_udp_t* handle, const char* m_addr, const char* i_addr, uv_membership membership) { + return uv_udp_set_membership(handle, m_addr, i_addr, membership); +} + +extern "C" int +rust_uv_udp_set_multicast_loop +(uv_udp_t* handle, int on) { + return uv_udp_set_multicast_loop(handle, on); +} + +extern "C" int +rust_uv_udp_set_multicast_ttl +(uv_udp_t* handle, int ttl) { + return uv_udp_set_multicast_ttl(handle, ttl); +} + +extern "C" int +rust_uv_udp_set_broadcast +(uv_udp_t* handle, int on) { + return uv_udp_set_broadcast(handle, on); +} + extern "C" int rust_uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb) { @@ -546,10 +658,34 @@ extern "C" void rust_uv_freeaddrinfo(addrinfo* res) { uv_freeaddrinfo(res); } + +extern "C" int +rust_uv_is_ipv4_sockaddr(sockaddr* addr) { + return addr->sa_family == AF_INET; +} + +extern "C" int +rust_uv_is_ipv6_sockaddr(sockaddr* addr) { + return addr->sa_family == AF_INET6; +} + +extern "C" sockaddr_in* +rust_uv_sockaddr_as_sockaddr_in(sockaddr* addr) { +// return (sockaddr_in*)addr->sa_data; + return (sockaddr_in*)addr; +} + +extern "C" sockaddr_in6* +rust_uv_sockaddr_as_sockaddr_in6(sockaddr* addr) { + //return (sockaddr_in6*)addr->sa_data; + return (sockaddr_in6*)addr; +} + extern "C" bool rust_uv_is_ipv4_addrinfo(addrinfo* input) { return input->ai_family == AF_INET; } + extern "C" bool rust_uv_is_ipv6_addrinfo(addrinfo* input) { return input->ai_family == AF_INET6; diff --git a/src/rt/rustrt.def.in b/src/rt/rustrt.def.in index 0da04e34f495d..ea614330866fe 100644 --- a/src/rt/rustrt.def.in +++ b/src/rt/rustrt.def.in @@ -39,6 +39,7 @@ rust_list_dir_wfd_size rust_list_dir_wfd_fp_buf rust_log_console_on rust_log_console_off +rust_should_log_console rust_set_environ rust_unset_sigprocmask rust_sched_current_nonlazy_threads @@ -54,7 +55,7 @@ rust_get_stack_segment rust_get_c_stack rust_log_str start_task -vec_reserve_shared_actual +rust_local_realloc task_clear_event_reject task_wait_event task_signal_event @@ -105,6 +106,29 @@ rust_uv_tcp_connect rust_uv_tcp_bind rust_uv_tcp_connect6 rust_uv_tcp_bind6 +rust_uv_tcp_getsockname +rust_uv_tcp_getsockname6 +rust_uv_tcp_nodelay +rust_uv_tcp_keepalive +rust_uv_tcp_simultaneous_accepts +rust_uv_udp_init +rust_uv_udp_bind +rust_uv_udp_bind6 +rust_uv_udp_send +rust_uv_udp_send6 +rust_uv_udp_recv_start +rust_uv_udp_recv_stop +rust_uv_get_udp_handle_from_send_req +rust_uv_udp_getsockname +rust_uv_udp_getsockname6 +rust_uv_udp_set_membership +rust_uv_udp_set_multicast_loop +rust_uv_udp_set_multicast_ttl +rust_uv_udp_set_broadcast +rust_uv_is_ipv4_sockaddr +rust_uv_is_ipv6_sockaddr +rust_uv_sockaddr_as_sockaddr_in +rust_uv_sockaddr_as_sockaddr_in6 rust_uv_listen rust_uv_accept rust_uv_write @@ -178,6 +202,7 @@ rust_task_deref 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 @@ -228,6 +253,7 @@ rust_delete_memory_region rust_new_boxed_region rust_delete_boxed_region rust_boxed_region_malloc +rust_boxed_region_realloc rust_boxed_region_free rust_try rust_begin_unwind @@ -239,3 +265,10 @@ rust_take_env_lock rust_drop_env_lock rust_update_log_settings rust_running_on_valgrind +rust_get_num_cpus +rust_get_global_args_ptr +rust_current_boxed_region +rust_take_global_args_lock +rust_drop_global_args_lock +rust_set_exit_status_newrt +rust_get_exit_status_newrt \ No newline at end of file diff --git a/src/test/run-pass/morestack6.rs b/src/test/run-pass/morestack6.rs index 1a670960f39c5..a46b7a3dfcff6 100644 --- a/src/test/run-pass/morestack6.rs +++ b/src/test/run-pass/morestack6.rs @@ -23,7 +23,6 @@ mod rustrt { pub fn rust_get_sched_id() -> libc::intptr_t; pub fn rust_get_argc() -> libc::c_int; pub fn get_task_id() -> libc::intptr_t; - pub fn rust_sched_threads(); pub fn rust_get_task(); } } @@ -31,7 +30,6 @@ mod rustrt { fn calllink01() { unsafe { rustrt::rust_get_sched_id(); } } fn calllink02() { unsafe { rustrt::rust_get_argc(); } } fn calllink08() { unsafe { rustrt::get_task_id(); } } -fn calllink09() { unsafe { rustrt::rust_sched_threads(); } } fn calllink10() { unsafe { rustrt::rust_get_task(); } } fn runtest(f: extern fn(), frame_backoff: u32) { @@ -64,7 +62,6 @@ pub fn main() { calllink01, calllink02, calllink08, - calllink09, calllink10 ]; let mut rng = rand::rng();