Skip to content

Commit f2ca549

Browse files
committed
Add sync.rs with counting blocking semaphores
1 parent bdbad61 commit f2ca549

File tree

2 files changed

+191
-1
lines changed

2 files changed

+191
-1
lines changed

src/libcore/core.rc

+2-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export float, f32, f64;
3939
export box, char, str, ptr, vec, at_vec, bool;
4040
export either, option, result, iter;
4141
export libc, os, io, run, rand, sys, unsafe, logging;
42-
export arc, comm, task, future, pipes;
42+
export arc, comm, task, future, pipes, sync;
4343
export extfmt;
4444
// The test harness links against core, so don't include runtime in tests.
4545
// FIXME (#2861): Uncomment this after snapshot gets updated.
@@ -204,6 +204,7 @@ mod comm;
204204
mod task;
205205
mod future;
206206
mod pipes;
207+
mod sync;
207208

208209
// Runtime and language-primitive support
209210

src/libcore/sync.rs

+189
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/**
2+
* The concurrency primitives you know and love.
3+
*
4+
* Maybe once we have a "core exports x only to std" mechanism, these can be
5+
* in std.
6+
*/
7+
8+
export semaphore, new_semaphore;
9+
10+
// FIXME (#3119) This shouldn't be a thing exported from core.
11+
import arc::exclusive;
12+
13+
// Each waiting task receives on one of these. FIXME #3125 make these oneshot.
14+
type wait_end = pipes::port<()>;
15+
type signal_end = pipes::chan<()>;
16+
// A doubly-ended queue of waiting tasks.
17+
type waitqueue = { head: pipes::port<signal_end>,
18+
tail: pipes::chan<signal_end> };
19+
20+
fn new_waiter() -> (signal_end, wait_end) { pipes::stream() }
21+
22+
/// A counting semaphore.
23+
enum semaphore = exclusive<{
24+
mut count: int,
25+
waiters: waitqueue,
26+
}>;
27+
28+
/// Create a new semaphore with the specified count.
29+
fn new_semaphore(count: int) -> semaphore {
30+
let (tail, head) = pipes::stream();
31+
semaphore(exclusive({ mut count: count,
32+
waiters: { head: head, tail: tail } }))
33+
}
34+
35+
impl semaphore for &semaphore {
36+
/// Creates a new handle to the semaphore.
37+
fn clone() -> semaphore {
38+
semaphore((**self).clone())
39+
}
40+
41+
/**
42+
* Acquires a resource represented by the semaphore. Blocks if necessary
43+
* until resource(s) become available.
44+
*/
45+
fn wait() {
46+
let mut waiter_nobe = none;
47+
unsafe {
48+
do (**self).with |state| {
49+
state.count -= 1;
50+
if state.count < 0 {
51+
let (signal_end,wait_end) = new_waiter();
52+
waiter_nobe = some(wait_end);
53+
// Enqueue ourself.
54+
state.waiters.tail.send(signal_end);
55+
}
56+
}
57+
}
58+
for 1000.times { task::yield(); }
59+
// Need to wait outside the exclusive.
60+
if waiter_nobe.is_some() {
61+
let _ = option::unwrap(waiter_nobe).recv();
62+
}
63+
}
64+
65+
/**
66+
* Release a held resource represented by the semaphore. Wakes a blocked
67+
* contending task, if any exist.
68+
*/
69+
fn signal() {
70+
unsafe {
71+
do (**self).with |state| {
72+
state.count += 1;
73+
// The peek is mandatory to make sure recv doesn't block.
74+
if state.count >= 0 && state.waiters.head.peek() {
75+
// Pop off the waitqueue and send a wakeup signal. If the
76+
// waiter was killed, its port will have closed, and send
77+
// will fail. Keep trying until we get a live task.
78+
state.waiters.head.recv().send(());
79+
// to-do: use this version when it's ready, kill-friendly.
80+
// while !state.waiters.head.recv().try_send(()) { }
81+
}
82+
}
83+
}
84+
}
85+
86+
/// Runs a function with ownership of one of the semaphore's resources.
87+
fn access<U>(blk: fn() -> U) -> U {
88+
self.wait();
89+
let _x = sem_release(self);
90+
blk()
91+
}
92+
}
93+
94+
// FIXME(#3136) should go inside of access()
95+
struct sem_release {
96+
sem: &semaphore;
97+
new(sem: &semaphore) { self.sem = sem; }
98+
drop { self.sem.signal(); }
99+
}
100+
101+
#[cfg(test)]
102+
mod tests {
103+
#[test]
104+
fn test_sem_as_mutex() {
105+
let s = ~new_semaphore(1);
106+
let s2 = ~s.clone();
107+
do task::spawn {
108+
do s2.access {
109+
for 10.times { task::yield(); }
110+
}
111+
}
112+
do s.access {
113+
for 10.times { task::yield(); }
114+
}
115+
}
116+
#[test]
117+
fn test_sem_as_cvar() {
118+
/* Child waits and parent signals */
119+
let (c,p) = pipes::stream();
120+
let s = ~new_semaphore(0);
121+
let s2 = ~s.clone();
122+
do task::spawn {
123+
s2.wait();
124+
c.send(());
125+
}
126+
for 10.times { task::yield(); }
127+
s.signal();
128+
let _ = p.recv();
129+
130+
/* Parent waits and child signals */
131+
let (c,p) = pipes::stream();
132+
let s = ~new_semaphore(0);
133+
let s2 = ~s.clone();
134+
do task::spawn {
135+
for 10.times { task::yield(); }
136+
s2.signal();
137+
let _ = p.recv();
138+
}
139+
s.wait();
140+
c.send(());
141+
}
142+
#[test]
143+
fn test_sem_mutual_exclusion() {
144+
let (c,p) = pipes::stream();
145+
let s = ~new_semaphore(1);
146+
let s2 = ~s.clone();
147+
let sharedstate = ~0;
148+
let ptr = ptr::addr_of(*sharedstate);
149+
do task::spawn {
150+
let sharedstate = unsafe { unsafe::reinterpret_cast(ptr) };
151+
access_shared(sharedstate, s2, 10);
152+
c.send(());
153+
}
154+
access_shared(sharedstate, s, 10);
155+
let _ = p.recv();
156+
157+
assert *sharedstate == 20;
158+
159+
fn access_shared(sharedstate: &mut int, sem: &semaphore, n: uint) {
160+
for n.times {
161+
do sem.access {
162+
let oldval = *sharedstate;
163+
task::yield();
164+
*sharedstate = oldval + 1;
165+
}
166+
}
167+
}
168+
}
169+
#[test]
170+
fn test_sem_runtime_friendly_blocking() {
171+
do task::spawn_sched(task::manual_threads(1)) {
172+
let s = ~new_semaphore(1);
173+
let s2 = ~s.clone();
174+
let (c,p) = pipes::stream();
175+
let child_data = ~mut some((s2,c));
176+
do s.access {
177+
let (s2,c) = option::swap_unwrap(child_data);
178+
do task::spawn {
179+
c.send(());
180+
do s2.access { }
181+
c.send(());
182+
}
183+
let _ = p.recv(); // wait for child to come alive
184+
for 5.times { task::yield(); } // let the child contend
185+
}
186+
let _ = p.recv(); // wait for child to be done
187+
}
188+
}
189+
}

0 commit comments

Comments
 (0)