diff options
| author | Ben Blum <bblum@andrew.cmu.edu> | 2012-08-10 20:46:19 -0400 |
|---|---|---|
| committer | Ben Blum <bblum@andrew.cmu.edu> | 2012-08-10 20:53:09 -0400 |
| commit | 0eae571389b0124eae4b33ae532788cff735da07 (patch) | |
| tree | 60e849f5a1341e542973ef8872043101654580a7 /src/libstd | |
| parent | 42825fbea659b555e7624c5a4f2e0f5bc3264c24 (diff) | |
| download | rust-0eae571389b0124eae4b33ae532788cff735da07.tar.gz rust-0eae571389b0124eae4b33ae532788cff735da07.zip | |
Implement mutex_arc and rw_arc; add some tests
Diffstat (limited to 'src/libstd')
| -rw-r--r-- | src/libstd/arc.rs | 273 | ||||
| -rw-r--r-- | src/libstd/std.rc | 1 |
2 files changed, 274 insertions, 0 deletions
diff --git a/src/libstd/arc.rs b/src/libstd/arc.rs index 6ce3481a053..49ec3b3c86d 100644 --- a/src/libstd/arc.rs +++ b/src/libstd/arc.rs @@ -5,8 +5,10 @@ import unsafe::{shared_mutable_state, clone_shared_mutable_state, get_shared_mutable_state, get_shared_immutable_state}; +import sync::{condvar, mutex, rwlock}; export arc, clone, get; +export mutex_arc, rw_arc; /**************************************************************************** * Immutable ARC @@ -43,10 +45,181 @@ fn clone<T: const send>(rc: &arc<T>) -> arc<T> { * Mutex protected ARC (unsafe) ****************************************************************************/ +struct mutex_arc_inner<T: send> { lock: mutex; failed: bool; data: T; } +/// An ARC with mutable data protected by a blocking mutex. +struct mutex_arc<T: send> { x: shared_mutable_state<mutex_arc_inner<T>>; } + +/// Create a mutex-protected ARC with the supplied data. +fn mutex_arc<T: send>(+user_data: T) -> mutex_arc<T> { + let data = mutex_arc_inner { + lock: mutex(), failed: false, data: user_data + }; + mutex_arc { x: unsafe { shared_mutable_state(data) } } +} + +impl<T: send> &mutex_arc<T> { + /// Duplicate a mutex-protected ARC, as arc::clone. + fn clone() -> mutex_arc<T> { + // NB: Cloning the underlying mutex is not necessary. Its reference + // count would be exactly the same as the shared state's. + mutex_arc { x: unsafe { clone_shared_mutable_state(&self.x) } } + } + + /** + * Access the underlying mutable data with mutual exclusion from other + * tasks. The argument closure will be run with the mutex locked; all + * other tasks wishing to access the data will block until the closure + * finishes running. + * + * The reason this function is 'unsafe' is because it is possible to + * construct a circular reference among multiple ARCs by mutating the + * underlying data. This creates potential for deadlock, but worse, this + * will guarantee a memory leak of all involved ARCs. Using mutex ARCs + * inside of other ARCs is safe in absence of circular references. + * + * If you wish to nest mutex_arcs, one strategy for ensuring safety at + * runtime is to add a "nesting level counter" inside the stored data, and + * when traversing the arcs, assert that they monotonically decrease. + * + * # Failure + * + * Failing while inside the ARC will unlock the ARC while unwinding, so + * that other tasks won't block forever. It will also poison the ARC: + * any tasks that subsequently try to access it (including those already + * blocked on the mutex) will also fail immediately. + */ + #[inline(always)] + unsafe fn access<U>(blk: fn(x: &mut T) -> U) -> U { + let state = unsafe { get_shared_mutable_state(&self.x) }; + // Borrowck would complain about this if the function were not already + // unsafe. See borrow_rwlock, far below. + do (&state.lock).lock { + check_poison(true, state.failed); + state.failed = true; + let result = blk(&mut state.data); + state.failed = false; + result + } + } +/* FIXME(#3145): Make this compile; borrowck doesn't like it..? + /// As access(), but with a condvar, as sync::mutex.lock_cond(). + #[inline(always)] + unsafe fn access_cond<U>(blk: fn(x: &mut T, condvar) -> U) -> U { + let state = unsafe { get_shared_mutable_state(&self.x) }; + do (&state.lock).lock_cond |cond| { + check_poison(true, state.failed); + state.failed = true; + let result = blk(&mut state.data, cond); + state.failed = false; + result + } + } +*/ +} + +// Common code for {mutex.access,rwlock.write}{,_cond}. +#[inline(always)] +fn check_poison(is_mutex: bool, failed: bool) { + if failed { + if is_mutex { + fail ~"Poisoned mutex_arc - another task failed inside!"; + } else { + fail ~"Poisoned rw_arc - another task failed inside!"; + } + } +} + /**************************************************************************** * R/W lock protected ARC ****************************************************************************/ +struct rw_arc_inner<T: const send> { lock: rwlock; failed: bool; data: T; } +/** + * A dual-mode ARC protected by a reader-writer lock. The data can be accessed + * mutably or immutably, and immutably-accessing tasks may run concurrently. + * + * Unlike mutex_arcs, rw_arcs are safe, because they cannot be nested. + */ +struct rw_arc<T: const send> { + x: shared_mutable_state<rw_arc_inner<T>>; + mut cant_nest: (); +} + +/// Create a reader/writer ARC with the supplied data. +fn rw_arc<T: const send>(+user_data: T) -> rw_arc<T> { + let data = rw_arc_inner { + lock: rwlock(), failed: false, data: user_data + }; + rw_arc { x: unsafe { shared_mutable_state(data) }, cant_nest: () } +} + +impl<T: const send> &rw_arc<T> { + /// Duplicate a rwlock-protected ARC, as arc::clone. + fn clone() -> rw_arc<T> { + rw_arc { x: unsafe { clone_shared_mutable_state(&self.x) }, + cant_nest: () } + } + + /** + * Access the underlying data mutably. Locks the rwlock in write mode; + * other readers and writers will block. + * + * # Failure + * + * Failing while inside the ARC will unlock the ARC while unwinding, so + * that other tasks won't block forever. As mutex_arc.access, it will also + * poison the ARC, so subsequent readers and writers will both also fail. + */ + #[inline(always)] + fn write<U>(blk: fn(x: &mut T) -> U) -> U { + let state = unsafe { get_shared_mutable_state(&self.x) }; + do borrow_rwlock(state).write { + check_poison(false, state.failed); + state.failed = true; + let result = blk(&mut state.data); + state.failed = false; + result + } + } +/* FIXME(#3145): Make this compile; borrowck doesn't like it..? + /// As write(), but with a condvar, as sync::rwlock.write_cond(). + #[inline(always)] + fn write_cond<U>(blk: fn(x: &mut T, condvar) -> U) -> U { + let state = unsafe { get_shared_mutable_state(&self.x) }; + do borrow_rwlock(state).write_cond |cond| { + check_poison(false, state.failed); + state.failed = true; + let result = blk(&mut state.data, cond); + state.failed = false; + result + } + } +*/ + /** + * Access the underlying data immutably. May run concurrently with other + * reading tasks. + * + * # Failure + * + * Failing will unlock the ARC while unwinding. However, unlike all other + * access modes, this will not poison the ARC. + */ + fn read<U>(blk: fn(x: &T) -> U) -> U { + let state = unsafe { get_shared_immutable_state(&self.x) }; + do (&state.lock).read { + check_poison(false, state.failed); + blk(&state.data) + } + } +} + +// Borrowck rightly complains about immutably aliasing the rwlock in order to +// lock it. This wraps the unsafety, with the justification that the 'lock' +// field is never overwritten; only 'failed' and 'data'. +fn borrow_rwlock<T: const send>(state: &mut rw_arc_inner<T>) -> &rwlock { + unsafe { unsafe::reinterpret_cast(&state.lock) } +} + /**************************************************************************** * Tests ****************************************************************************/ @@ -80,4 +253,104 @@ mod tests { log(info, arc_v); } + + #[test] #[should_fail] #[ignore(cfg(windows))] + fn test_mutex_arc_poison() { + let arc = ~mutex_arc(1); + let arc2 = ~arc.clone(); + do task::try { + do arc2.access |one| { + assert *one == 2; + } + }; + do arc.access |one| { + assert *one == 1; + } + } + #[test] #[should_fail] #[ignore(cfg(windows))] + fn test_rw_arc_poison_wr() { + let arc = ~rw_arc(1); + let arc2 = ~arc.clone(); + do task::try { + do arc2.write |one| { + assert *one == 2; + } + }; + do arc.read |one| { + assert *one == 1; + } + } + #[test] #[should_fail] #[ignore(cfg(windows))] + fn test_rw_arc_poison_ww() { + let arc = ~rw_arc(1); + let arc2 = ~arc.clone(); + do task::try { + do arc2.write |one| { + assert *one == 2; + } + }; + do arc.write |one| { + assert *one == 1; + } + } + #[test] #[ignore(cfg(windows))] + fn test_rw_arc_no_poison_rr() { + let arc = ~rw_arc(1); + let arc2 = ~arc.clone(); + do task::try { + do arc2.read |one| { + assert *one == 2; + } + }; + do arc.read |one| { + assert *one == 1; + } + } + #[test] #[ignore(cfg(windows))] + fn test_rw_arc_no_poison_rw() { + let arc = ~rw_arc(1); + let arc2 = ~arc.clone(); + do task::try { + do arc2.read |one| { + assert *one == 2; + } + }; + do arc.write |one| { + assert *one == 1; + } + } + + #[test] + fn test_rw_arc() { + let arc = ~rw_arc(0); + let arc2 = ~arc.clone(); + let (c,p) = pipes::stream(); + + do task::spawn { + do arc2.write |num| { + for 10.times { + let tmp = *num; + *num = -1; + task::yield(); + *num = tmp + 1; + } + c.send(()); + } + } + // Readers try to catch the writer in the act + let mut children = ~[]; + for 5.times { + let arc3 = ~arc.clone(); + do task::task().future_result(|+r| vec::push(children, r)).spawn { + do arc3.read |num| { + assert *num >= 0; + } + } + } + // Wait for children to pass their asserts + for vec::each(children) |r| { future::get(r); } + // Wait for writer to finish + p.recv(); + do arc.read |num| { assert *num == 10; } + } } diff --git a/src/libstd/std.rc b/src/libstd/std.rc index bc3c6fb6513..dfc659781de 100644 --- a/src/libstd/std.rc +++ b/src/libstd/std.rc @@ -18,6 +18,7 @@ import core::*; export net, net_tcp, net_ip, net_url; export uv, uv_ll, uv_iotask, uv_global_loop; export c_vec, timer; +export sync, arc; export bitv, deque, fun_treemap, list, map; export smallintmap, sort, treemap; export rope, arena, par; |
