Fix :broadcast_heavy in condvars.

This commit is contained in:
Ben Blum 2012-08-15 14:11:39 -04:00
parent 5f64d5df33
commit a57d3e0c15

View file

@ -28,6 +28,11 @@ type signal_end = pipes::chan_one<()>;
struct waitqueue { head: pipes::port<signal_end>; struct waitqueue { head: pipes::port<signal_end>;
tail: pipes::chan<signal_end>; } tail: pipes::chan<signal_end>; }
fn new_waitqueue() -> waitqueue {
let (block_tail, block_head) = pipes::stream();
waitqueue { head: block_head, tail: block_tail }
}
// Signals one live task from the queue. // Signals one live task from the queue.
#[doc(hidden)] #[doc(hidden)]
fn signal_waitqueue(q: &waitqueue) -> bool { fn signal_waitqueue(q: &waitqueue) -> bool {
@ -70,18 +75,15 @@ enum sem<Q: send> = Exclusive<sem_inner<Q>>;
#[doc(hidden)] #[doc(hidden)]
fn new_sem<Q: send>(count: int, +q: Q) -> sem<Q> { fn new_sem<Q: send>(count: int, +q: Q) -> sem<Q> {
let (wait_tail, wait_head) = pipes::stream();
sem(exclusive(sem_inner { sem(exclusive(sem_inner {
mut count: count, mut count: count, waiters: new_waitqueue(), blocked: q }))
waiters: waitqueue { head: wait_head, tail: wait_tail },
blocked: q }))
} }
#[doc(hidden)] #[doc(hidden)]
fn new_sem_and_signal(count: int, num_condvars: uint) -> sem<~[waitqueue]> { fn new_sem_and_signal(count: int, num_condvars: uint)
let mut queues = ~[]; -> sem<~[mut waitqueue]> {
let mut queues = ~[mut];
for num_condvars.times { for num_condvars.times {
let (block_tail, block_head) = pipes::stream(); vec::push(queues, new_waitqueue());
vec::push(queues, waitqueue { head: block_head, tail: block_tail });
} }
new_sem(count, queues) new_sem(count, queues)
} }
@ -136,7 +138,7 @@ impl &sem<()> {
} }
} }
#[doc(hidden)] #[doc(hidden)]
impl &sem<~[waitqueue]> { impl &sem<~[mut waitqueue]> {
fn access<U>(blk: fn() -> U) -> U { fn access<U>(blk: fn() -> U) -> U {
let mut release = none; let mut release = none;
unsafe { unsafe {
@ -158,13 +160,13 @@ struct sem_release {
} }
#[doc(hidden)] #[doc(hidden)]
struct sem_and_signal_release { struct sem_and_signal_release {
sem: &sem<~[waitqueue]>; sem: &sem<~[mut waitqueue]>;
new(sem: &sem<~[waitqueue]>) { self.sem = sem; } new(sem: &sem<~[mut waitqueue]>) { self.sem = sem; }
drop { self.sem.release(); } drop { self.sem.release(); }
} }
/// A mechanism for atomic-unlock-and-deschedule blocking and signalling. /// A mechanism for atomic-unlock-and-deschedule blocking and signalling.
struct condvar { priv sem: &sem<~[waitqueue]>; drop { } } struct condvar { priv sem: &sem<~[mut waitqueue]>; drop { } }
impl &condvar { impl &condvar {
/** /**
@ -232,8 +234,8 @@ impl &condvar {
// mutex during unwinding. As long as the wrapper (mutex, etc) is // mutex during unwinding. As long as the wrapper (mutex, etc) is
// bounded in when it gets released, this shouldn't hang forever. // bounded in when it gets released, this shouldn't hang forever.
struct sem_and_signal_reacquire { struct sem_and_signal_reacquire {
sem: &sem<~[waitqueue]>; sem: &sem<~[mut waitqueue]>;
new(sem: &sem<~[waitqueue]>) { self.sem = sem; } new(sem: &sem<~[mut waitqueue]>) { self.sem = sem; }
drop unsafe { drop unsafe {
// Needs to succeed, instead of itself dying. // Needs to succeed, instead of itself dying.
do task::unkillable { do task::unkillable {
@ -268,19 +270,23 @@ impl &condvar {
/// As broadcast, but with a specified condvar_id. See wait_on. /// As broadcast, but with a specified condvar_id. See wait_on.
fn broadcast_on(condvar_id: uint) -> uint { fn broadcast_on(condvar_id: uint) -> uint {
let mut out_of_bounds = none; let mut out_of_bounds = none;
let mut result = 0; let mut queue = none;
unsafe { unsafe {
do (**self.sem).with |state| { do (**self.sem).with |state| {
if condvar_id < vec::len(state.blocked) { if condvar_id < vec::len(state.blocked) {
// FIXME(#3145) fix :broadcast_heavy // To avoid :broadcast_heavy, we make a new waitqueue,
result = broadcast_waitqueue(&state.blocked[condvar_id]) // swap it out with the old one, and broadcast on the
// old one outside of the little-lock.
queue = some(util::replace(&mut state.blocked[condvar_id],
new_waitqueue()));
} else { } else {
out_of_bounds = some(vec::len(state.blocked)); out_of_bounds = some(vec::len(state.blocked));
} }
} }
} }
do check_cvar_bounds(out_of_bounds, condvar_id, "cond.signal_on()") { do check_cvar_bounds(out_of_bounds, condvar_id, "cond.signal_on()") {
result let queue = option::swap_unwrap(&mut queue);
broadcast_waitqueue(&queue)
} }
} }
} }
@ -303,7 +309,7 @@ fn check_cvar_bounds<U>(out_of_bounds: option<uint>, id: uint, act: &str,
} }
#[doc(hidden)] #[doc(hidden)]
impl &sem<~[waitqueue]> { impl &sem<~[mut waitqueue]> {
// The only other place that condvars get built is rwlock_write_mode. // The only other place that condvars get built is rwlock_write_mode.
fn access_cond<U>(blk: fn(c: &condvar) -> U) -> U { fn access_cond<U>(blk: fn(c: &condvar) -> U) -> U {
do self.access { blk(&condvar { sem: self }) } do self.access { blk(&condvar { sem: self }) }
@ -354,7 +360,7 @@ impl &semaphore {
* A task which fails while holding a mutex will unlock the mutex as it * A task which fails while holding a mutex will unlock the mutex as it
* unwinds. * unwinds.
*/ */
struct mutex { priv sem: sem<~[waitqueue]>; } struct mutex { priv sem: sem<~[mut waitqueue]>; }
/// Create a new mutex, with one associated condvar. /// Create a new mutex, with one associated condvar.
fn mutex() -> mutex { mutex_with_condvars(1) } fn mutex() -> mutex { mutex_with_condvars(1) }
@ -402,7 +408,7 @@ struct rwlock_inner {
*/ */
struct rwlock { struct rwlock {
/* priv */ order_lock: semaphore; /* priv */ order_lock: semaphore;
/* priv */ access_lock: sem<~[waitqueue]>; /* priv */ access_lock: sem<~[mut waitqueue]>;
/* priv */ state: Exclusive<rwlock_inner>; /* priv */ state: Exclusive<rwlock_inner>;
} }