Skip to content

Commit 0eae571

Browse files
committed
Implement mutex_arc and rw_arc; add some tests
1 parent 42825fb commit 0eae571

File tree

2 files changed

+274
-0
lines changed

2 files changed

+274
-0
lines changed

src/libstd/arc.rs

Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55

66
import unsafe::{shared_mutable_state, clone_shared_mutable_state,
77
get_shared_mutable_state, get_shared_immutable_state};
8+
import sync::{condvar, mutex, rwlock};
89

910
export arc, clone, get;
11+
export mutex_arc, rw_arc;
1012

1113
/****************************************************************************
1214
* Immutable ARC
@@ -43,10 +45,181 @@ fn clone<T: const send>(rc: &arc<T>) -> arc<T> {
4345
* Mutex protected ARC (unsafe)
4446
****************************************************************************/
4547

48+
struct mutex_arc_inner<T: send> { lock: mutex; failed: bool; data: T; }
49+
/// An ARC with mutable data protected by a blocking mutex.
50+
struct mutex_arc<T: send> { x: shared_mutable_state<mutex_arc_inner<T>>; }
51+
52+
/// Create a mutex-protected ARC with the supplied data.
53+
fn mutex_arc<T: send>(+user_data: T) -> mutex_arc<T> {
54+
let data = mutex_arc_inner {
55+
lock: mutex(), failed: false, data: user_data
56+
};
57+
mutex_arc { x: unsafe { shared_mutable_state(data) } }
58+
}
59+
60+
impl<T: send> &mutex_arc<T> {
61+
/// Duplicate a mutex-protected ARC, as arc::clone.
62+
fn clone() -> mutex_arc<T> {
63+
// NB: Cloning the underlying mutex is not necessary. Its reference
64+
// count would be exactly the same as the shared state's.
65+
mutex_arc { x: unsafe { clone_shared_mutable_state(&self.x) } }
66+
}
67+
68+
/**
69+
* Access the underlying mutable data with mutual exclusion from other
70+
* tasks. The argument closure will be run with the mutex locked; all
71+
* other tasks wishing to access the data will block until the closure
72+
* finishes running.
73+
*
74+
* The reason this function is 'unsafe' is because it is possible to
75+
* construct a circular reference among multiple ARCs by mutating the
76+
* underlying data. This creates potential for deadlock, but worse, this
77+
* will guarantee a memory leak of all involved ARCs. Using mutex ARCs
78+
* inside of other ARCs is safe in absence of circular references.
79+
*
80+
* If you wish to nest mutex_arcs, one strategy for ensuring safety at
81+
* runtime is to add a "nesting level counter" inside the stored data, and
82+
* when traversing the arcs, assert that they monotonically decrease.
83+
*
84+
* # Failure
85+
*
86+
* Failing while inside the ARC will unlock the ARC while unwinding, so
87+
* that other tasks won't block forever. It will also poison the ARC:
88+
* any tasks that subsequently try to access it (including those already
89+
* blocked on the mutex) will also fail immediately.
90+
*/
91+
#[inline(always)]
92+
unsafe fn access<U>(blk: fn(x: &mut T) -> U) -> U {
93+
let state = unsafe { get_shared_mutable_state(&self.x) };
94+
// Borrowck would complain about this if the function were not already
95+
// unsafe. See borrow_rwlock, far below.
96+
do (&state.lock).lock {
97+
check_poison(true, state.failed);
98+
state.failed = true;
99+
let result = blk(&mut state.data);
100+
state.failed = false;
101+
result
102+
}
103+
}
104+
/* FIXME(#3145): Make this compile; borrowck doesn't like it..?
105+
/// As access(), but with a condvar, as sync::mutex.lock_cond().
106+
#[inline(always)]
107+
unsafe fn access_cond<U>(blk: fn(x: &mut T, condvar) -> U) -> U {
108+
let state = unsafe { get_shared_mutable_state(&self.x) };
109+
do (&state.lock).lock_cond |cond| {
110+
check_poison(true, state.failed);
111+
state.failed = true;
112+
let result = blk(&mut state.data, cond);
113+
state.failed = false;
114+
result
115+
}
116+
}
117+
*/
118+
}
119+
120+
// Common code for {mutex.access,rwlock.write}{,_cond}.
121+
#[inline(always)]
122+
fn check_poison(is_mutex: bool, failed: bool) {
123+
if failed {
124+
if is_mutex {
125+
fail ~"Poisoned mutex_arc - another task failed inside!";
126+
} else {
127+
fail ~"Poisoned rw_arc - another task failed inside!";
128+
}
129+
}
130+
}
131+
46132
/****************************************************************************
47133
* R/W lock protected ARC
48134
****************************************************************************/
49135

136+
struct rw_arc_inner<T: const send> { lock: rwlock; failed: bool; data: T; }
137+
/**
138+
* A dual-mode ARC protected by a reader-writer lock. The data can be accessed
139+
* mutably or immutably, and immutably-accessing tasks may run concurrently.
140+
*
141+
* Unlike mutex_arcs, rw_arcs are safe, because they cannot be nested.
142+
*/
143+
struct rw_arc<T: const send> {
144+
x: shared_mutable_state<rw_arc_inner<T>>;
145+
mut cant_nest: ();
146+
}
147+
148+
/// Create a reader/writer ARC with the supplied data.
149+
fn rw_arc<T: const send>(+user_data: T) -> rw_arc<T> {
150+
let data = rw_arc_inner {
151+
lock: rwlock(), failed: false, data: user_data
152+
};
153+
rw_arc { x: unsafe { shared_mutable_state(data) }, cant_nest: () }
154+
}
155+
156+
impl<T: const send> &rw_arc<T> {
157+
/// Duplicate a rwlock-protected ARC, as arc::clone.
158+
fn clone() -> rw_arc<T> {
159+
rw_arc { x: unsafe { clone_shared_mutable_state(&self.x) },
160+
cant_nest: () }
161+
}
162+
163+
/**
164+
* Access the underlying data mutably. Locks the rwlock in write mode;
165+
* other readers and writers will block.
166+
*
167+
* # Failure
168+
*
169+
* Failing while inside the ARC will unlock the ARC while unwinding, so
170+
* that other tasks won't block forever. As mutex_arc.access, it will also
171+
* poison the ARC, so subsequent readers and writers will both also fail.
172+
*/
173+
#[inline(always)]
174+
fn write<U>(blk: fn(x: &mut T) -> U) -> U {
175+
let state = unsafe { get_shared_mutable_state(&self.x) };
176+
do borrow_rwlock(state).write {
177+
check_poison(false, state.failed);
178+
state.failed = true;
179+
let result = blk(&mut state.data);
180+
state.failed = false;
181+
result
182+
}
183+
}
184+
/* FIXME(#3145): Make this compile; borrowck doesn't like it..?
185+
/// As write(), but with a condvar, as sync::rwlock.write_cond().
186+
#[inline(always)]
187+
fn write_cond<U>(blk: fn(x: &mut T, condvar) -> U) -> U {
188+
let state = unsafe { get_shared_mutable_state(&self.x) };
189+
do borrow_rwlock(state).write_cond |cond| {
190+
check_poison(false, state.failed);
191+
state.failed = true;
192+
let result = blk(&mut state.data, cond);
193+
state.failed = false;
194+
result
195+
}
196+
}
197+
*/
198+
/**
199+
* Access the underlying data immutably. May run concurrently with other
200+
* reading tasks.
201+
*
202+
* # Failure
203+
*
204+
* Failing will unlock the ARC while unwinding. However, unlike all other
205+
* access modes, this will not poison the ARC.
206+
*/
207+
fn read<U>(blk: fn(x: &T) -> U) -> U {
208+
let state = unsafe { get_shared_immutable_state(&self.x) };
209+
do (&state.lock).read {
210+
check_poison(false, state.failed);
211+
blk(&state.data)
212+
}
213+
}
214+
}
215+
216+
// Borrowck rightly complains about immutably aliasing the rwlock in order to
217+
// lock it. This wraps the unsafety, with the justification that the 'lock'
218+
// field is never overwritten; only 'failed' and 'data'.
219+
fn borrow_rwlock<T: const send>(state: &mut rw_arc_inner<T>) -> &rwlock {
220+
unsafe { unsafe::reinterpret_cast(&state.lock) }
221+
}
222+
50223
/****************************************************************************
51224
* Tests
52225
****************************************************************************/
@@ -80,4 +253,104 @@ mod tests {
80253

81254
log(info, arc_v);
82255
}
256+
257+
#[test] #[should_fail] #[ignore(cfg(windows))]
258+
fn test_mutex_arc_poison() {
259+
let arc = ~mutex_arc(1);
260+
let arc2 = ~arc.clone();
261+
do task::try {
262+
do arc2.access |one| {
263+
assert *one == 2;
264+
}
265+
};
266+
do arc.access |one| {
267+
assert *one == 1;
268+
}
269+
}
270+
#[test] #[should_fail] #[ignore(cfg(windows))]
271+
fn test_rw_arc_poison_wr() {
272+
let arc = ~rw_arc(1);
273+
let arc2 = ~arc.clone();
274+
do task::try {
275+
do arc2.write |one| {
276+
assert *one == 2;
277+
}
278+
};
279+
do arc.read |one| {
280+
assert *one == 1;
281+
}
282+
}
283+
#[test] #[should_fail] #[ignore(cfg(windows))]
284+
fn test_rw_arc_poison_ww() {
285+
let arc = ~rw_arc(1);
286+
let arc2 = ~arc.clone();
287+
do task::try {
288+
do arc2.write |one| {
289+
assert *one == 2;
290+
}
291+
};
292+
do arc.write |one| {
293+
assert *one == 1;
294+
}
295+
}
296+
#[test] #[ignore(cfg(windows))]
297+
fn test_rw_arc_no_poison_rr() {
298+
let arc = ~rw_arc(1);
299+
let arc2 = ~arc.clone();
300+
do task::try {
301+
do arc2.read |one| {
302+
assert *one == 2;
303+
}
304+
};
305+
do arc.read |one| {
306+
assert *one == 1;
307+
}
308+
}
309+
#[test] #[ignore(cfg(windows))]
310+
fn test_rw_arc_no_poison_rw() {
311+
let arc = ~rw_arc(1);
312+
let arc2 = ~arc.clone();
313+
do task::try {
314+
do arc2.read |one| {
315+
assert *one == 2;
316+
}
317+
};
318+
do arc.write |one| {
319+
assert *one == 1;
320+
}
321+
}
322+
323+
#[test]
324+
fn test_rw_arc() {
325+
let arc = ~rw_arc(0);
326+
let arc2 = ~arc.clone();
327+
let (c,p) = pipes::stream();
328+
329+
do task::spawn {
330+
do arc2.write |num| {
331+
for 10.times {
332+
let tmp = *num;
333+
*num = -1;
334+
task::yield();
335+
*num = tmp + 1;
336+
}
337+
c.send(());
338+
}
339+
}
340+
// Readers try to catch the writer in the act
341+
let mut children = ~[];
342+
for 5.times {
343+
let arc3 = ~arc.clone();
344+
do task::task().future_result(|+r| vec::push(children, r)).spawn {
345+
do arc3.read |num| {
346+
assert *num >= 0;
347+
}
348+
}
349+
}
350+
// Wait for children to pass their asserts
351+
for vec::each(children) |r| { future::get(r); }
352+
// Wait for writer to finish
353+
p.recv();
354+
do arc.read |num| { assert *num == 10; }
355+
}
83356
}

src/libstd/std.rc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import core::*;
1818
export net, net_tcp, net_ip, net_url;
1919
export uv, uv_ll, uv_iotask, uv_global_loop;
2020
export c_vec, timer;
21+
export sync, arc;
2122
export bitv, deque, fun_treemap, list, map;
2223
export smallintmap, sort, treemap;
2324
export rope, arena, par;

0 commit comments

Comments
 (0)