Skip to content

Commit d1fc736

Browse files
committed
Enable multiple condvars on a single mutex/rwlock.
1 parent f6f9333 commit d1fc736

File tree

1 file changed

+172
-38
lines changed

1 file changed

+172
-38
lines changed

src/libstd/sync.rs

Lines changed: 172 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,13 @@ fn new_sem<Q: send>(count: int, +q: Q) -> sem<Q> {
6767
waiters: waitqueue { head: wait_head, tail: wait_tail },
6868
blocked: q }))
6969
}
70-
fn new_sem_and_signal(count: int) -> sem<waitqueue> {
71-
let (block_tail, block_head) = pipes::stream();
72-
new_sem(count, waitqueue { head: block_head, tail: block_tail })
70+
fn new_sem_and_signal(count: int, num_condvars: uint) -> sem<~[waitqueue]> {
71+
let mut queues = ~[];
72+
for num_condvars.times {
73+
let (block_tail, block_head) = pipes::stream();
74+
vec::push(queues, waitqueue { head: block_head, tail: block_tail });
75+
}
76+
new_sem(count, queues)
7377
}
7478

7579
impl<Q: send> &sem<Q> {
@@ -119,7 +123,7 @@ impl &sem<()> {
119123
blk()
120124
}
121125
}
122-
impl &sem<waitqueue> {
126+
impl &sem<~[waitqueue]> {
123127
fn access<U>(blk: fn() -> U) -> U {
124128
let mut release = none;
125129
unsafe {
@@ -139,52 +143,75 @@ struct sem_release {
139143
drop { self.sem.release(); }
140144
}
141145
struct sem_and_signal_release {
142-
sem: &sem<waitqueue>;
143-
new(sem: &sem<waitqueue>) { self.sem = sem; }
146+
sem: &sem<~[waitqueue]>;
147+
new(sem: &sem<~[waitqueue]>) { self.sem = sem; }
144148
drop { self.sem.release(); }
145149
}
146150

147151
/// A mechanism for atomic-unlock-and-deschedule blocking and signalling.
148-
struct condvar { priv sem: &sem<waitqueue>; drop { } }
152+
struct condvar { priv sem: &sem<~[waitqueue]>; drop { } }
149153

150154
impl &condvar {
151155
/// Atomically drop the associated lock, and block until a signal is sent.
152-
fn wait() {
156+
fn wait() { self.wait_on(0) }
157+
/**
158+
* As wait(), but can specify which of multiple condition variables to
159+
* wait on. Only a signal_on() or broadcast_on() with the same condvar_id
160+
* will wake this thread.
161+
*
162+
* The associated lock must have been initialised with an appropriate
163+
* number of condvars. The condvar_id must be between 0 and num_condvars-1
164+
* or else this call will fail.
165+
*
166+
* wait() is equivalent to wait_on(0).
167+
*/
168+
fn wait_on(condvar_id: uint) {
153169
// Create waiter nobe.
154170
let (signal_end, wait_end) = pipes::oneshot();
171+
let mut wait_end = some(wait_end);
155172
let mut signal_end = some(signal_end);
156173
let mut reacquire = none;
174+
let mut out_of_bounds = none;
157175
unsafe {
158176
do task::unkillable {
177+
// Release lock, 'atomically' enqueuing ourselves in so doing.
178+
do (**self.sem).with |state| {
179+
if condvar_id < vec::len(state.blocked) {
180+
// Drop the lock.
181+
state.count += 1;
182+
if state.count <= 0 {
183+
signal_waitqueue(&state.waiters);
184+
}
185+
// Enqueue ourself to be woken up by a signaller.
186+
let signal_end = option::swap_unwrap(&mut signal_end);
187+
state.blocked[condvar_id].tail.send(signal_end);
188+
} else {
189+
out_of_bounds = some(vec::len(state.blocked));
190+
}
191+
}
192+
159193
// If yield checks start getting inserted anywhere, we can be
160194
// killed before or after enqueueing. Deciding whether to
161195
// unkillably reacquire the lock needs to happen atomically
162196
// wrt enqueuing.
163-
reacquire = some(sem_and_signal_reacquire(self.sem));
164-
165-
// Release lock, 'atomically' enqueuing ourselves in so doing.
166-
do (**self.sem).with |state| {
167-
// Drop the lock.
168-
state.count += 1;
169-
if state.count <= 0 {
170-
signal_waitqueue(&state.waiters);
171-
}
172-
// Enqueue ourself to be woken up by a signaller.
173-
let signal_end = option::swap_unwrap(&mut signal_end);
174-
state.blocked.tail.send(signal_end);
197+
if out_of_bounds.is_none() {
198+
reacquire = some(sem_and_signal_reacquire(self.sem));
175199
}
176200
}
177201
}
178-
// Unconditionally "block". (Might not actually block if a signaller
179-
// did send -- I mean 'unconditionally' in contrast with acquire().)
180-
let _ = pipes::recv_one(wait_end);
202+
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.wait_on()") {
203+
// Unconditionally "block". (Might not actually block if a
204+
// signaller already sent -- I mean 'unconditionally' in contrast
205+
// with acquire().)
206+
let _ = pipes::recv_one(option::swap_unwrap(&mut wait_end));
207+
}
181208

182209
// This is needed for a failing condition variable to reacquire the
183210
// mutex during unwinding. As long as the wrapper (mutex, etc) is
184211
// bounded in when it gets released, this shouldn't hang forever.
185212
struct sem_and_signal_reacquire {
186-
sem: &sem<waitqueue>;
187-
new(sem: &sem<waitqueue>) { self.sem = sem; }
213+
sem: &sem<~[waitqueue]>;
214+
new(sem: &sem<~[waitqueue]>) { self.sem = sem; }
188215
drop unsafe {
189216
// Needs to succeed, instead of itself dying.
190217
do task::unkillable {
@@ -195,26 +222,64 @@ impl &condvar {
195222
}
196223

197224
/// Wake up a blocked task. Returns false if there was no blocked task.
198-
fn signal() -> bool {
225+
fn signal() -> bool { self.signal_on(0) }
226+
/// As signal, but with a specified condvar_id. See wait_on.
227+
fn signal_on(condvar_id: uint) -> bool {
228+
let mut out_of_bounds = none;
229+
let mut result = false;
199230
unsafe {
200231
do (**self.sem).with |state| {
201-
signal_waitqueue(&state.blocked)
232+
if condvar_id < vec::len(state.blocked) {
233+
result = signal_waitqueue(&state.blocked[condvar_id]);
234+
} else {
235+
out_of_bounds = some(vec::len(state.blocked));
236+
}
202237
}
203238
}
239+
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.signal_on()") {
240+
result
241+
}
204242
}
205243

206244
/// Wake up all blocked tasks. Returns the number of tasks woken.
207-
fn broadcast() -> uint {
245+
fn broadcast() -> uint { self.broadcast_on(0) }
246+
/// As broadcast, but with a specified condvar_id. See wait_on.
247+
fn broadcast_on(condvar_id: uint) -> uint {
248+
let mut out_of_bounds = none;
249+
let mut result = 0;
208250
unsafe {
209251
do (**self.sem).with |state| {
210-
// FIXME(#3145) fix :broadcast_heavy
211-
broadcast_waitqueue(&state.blocked)
252+
if condvar_id < vec::len(state.blocked) {
253+
// FIXME(#3145) fix :broadcast_heavy
254+
result = broadcast_waitqueue(&state.blocked[condvar_id])
255+
} else {
256+
out_of_bounds = some(vec::len(state.blocked));
257+
}
212258
}
213259
}
260+
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.signal_on()") {
261+
result
262+
}
263+
}
264+
}
265+
266+
// Checks whether a condvar ID was out of bounds, and fails if so, or does
267+
// something else next on success.
268+
#[inline(always)]
269+
fn check_cvar_bounds<U>(out_of_bounds: option<uint>, id: uint, act: &str,
270+
blk: fn() -> U) -> U {
271+
match out_of_bounds {
272+
some(0) =>
273+
fail fmt!("%s with illegal ID %u - this lock has no condvars!",
274+
act, id),
275+
some(length) =>
276+
fail fmt!("%s with illegal ID %u - ID must be less than %u",
277+
act, id, length),
278+
none => blk()
214279
}
215280
}
216281

217-
impl &sem<waitqueue> {
282+
impl &sem<~[waitqueue]> {
218283
// The only other place that condvars get built is rwlock_write_mode.
219284
fn access_cond<U>(blk: fn(c: &condvar) -> U) -> U {
220285
do self.access { blk(&condvar { sem: self }) }
@@ -263,10 +328,19 @@ impl &semaphore {
263328
* FIFO condition variable.
264329
* FIXME(#3145): document killability
265330
*/
266-
struct mutex { priv sem: sem<waitqueue>; }
331+
struct mutex { priv sem: sem<~[waitqueue]>; }
267332

268-
/// Create a new mutex.
269-
fn mutex() -> mutex { mutex { sem: new_sem_and_signal(1) } }
333+
/// Create a new mutex, with one associated condvar.
334+
fn mutex() -> mutex { mutex_with_condvars(1) }
335+
/**
336+
* Create a new mutex, with a specified number of associated condvars. This
337+
* will allow calling wait_on/signal_on/broadcast_on with condvar IDs between
338+
* 0 and num_condvars-1. (If num_condvars is 0, lock_cond will be allowed but
339+
* any operations on the condvar will fail.)
340+
*/
341+
fn mutex_with_condvars(num_condvars: uint) -> mutex {
342+
mutex { sem: new_sem_and_signal(1, num_condvars) }
343+
}
270344

271345
impl &mutex {
272346
/// Create a new handle to the mutex.
@@ -295,13 +369,20 @@ struct rwlock_inner {
295369
/// A blocking, no-starvation, reader-writer lock with an associated condvar.
296370
struct rwlock {
297371
/* priv */ order_lock: semaphore;
298-
/* priv */ access_lock: sem<waitqueue>;
372+
/* priv */ access_lock: sem<~[waitqueue]>;
299373
/* priv */ state: Exclusive<rwlock_inner>;
300374
}
301375

302-
/// Create a new rwlock.
303-
fn rwlock() -> rwlock {
304-
rwlock { order_lock: semaphore(1), access_lock: new_sem_and_signal(1),
376+
/// Create a new rwlock, with one associated condvar.
377+
fn rwlock() -> rwlock { rwlock_with_condvars(1) }
378+
379+
/**
380+
* Create a new rwlock, with a specified number of associated condvars.
381+
* Similar to mutex_with_condvars.
382+
*/
383+
fn rwlock_with_condvars(num_condvars: uint) -> rwlock {
384+
rwlock { order_lock: semaphore(1),
385+
access_lock: new_sem_and_signal(1, num_condvars),
305386
state: exclusive(rwlock_inner { read_mode: false,
306387
read_count: 0 }) }
307388
}
@@ -813,6 +894,59 @@ mod tests {
813894
drop { self.c.send(()); }
814895
}
815896
}
897+
#[test]
898+
fn test_mutex_cond_signal_on_0() {
899+
// Tests that signal_on(0) is equivalent to signal().
900+
let m = ~mutex();
901+
do m.lock_cond |cond| {
902+
let m2 = ~m.clone();
903+
do task::spawn {
904+
do m2.lock_cond |cond| {
905+
cond.signal_on(0);
906+
}
907+
}
908+
cond.wait();
909+
}
910+
}
911+
#[test] #[ignore(cfg(windows))]
912+
fn test_mutex_different_conds() {
913+
let result = do task::try {
914+
let m = ~mutex_with_condvars(2);
915+
let m2 = ~m.clone();
916+
let (c,p) = pipes::stream();
917+
do task::spawn {
918+
do m2.lock_cond |cond| {
919+
c.send(());
920+
cond.wait_on(1);
921+
}
922+
}
923+
let _ = p.recv();
924+
do m.lock_cond |cond| {
925+
if !cond.signal_on(0) {
926+
fail; // success; punt sibling awake.
927+
}
928+
}
929+
};
930+
assert result.is_err();
931+
}
932+
#[test] #[ignore(cfg(windows))]
933+
fn test_mutex_no_condvars() {
934+
let result = do task::try {
935+
let m = ~mutex_with_condvars(0);
936+
do m.lock_cond |cond| { cond.wait(); }
937+
};
938+
assert result.is_err();
939+
let result = do task::try {
940+
let m = ~mutex_with_condvars(0);
941+
do m.lock_cond |cond| { cond.signal(); }
942+
};
943+
assert result.is_err();
944+
let result = do task::try {
945+
let m = ~mutex_with_condvars(0);
946+
do m.lock_cond |cond| { cond.broadcast(); }
947+
};
948+
assert result.is_err();
949+
}
816950
/************************************************************************
817951
* Reader/writer lock tests
818952
************************************************************************/

0 commit comments

Comments
 (0)