diff options
| author | Alex Crichton <alex@alexcrichton.com> | 2013-12-15 22:19:34 -0800 |
|---|---|---|
| committer | Alex Crichton <alex@alexcrichton.com> | 2013-12-24 19:59:54 -0800 |
| commit | 962af9198f8f2a1e2d5121ac216b6f4d574ae54c (patch) | |
| tree | f990add092e17494929ab8c875254009f58ba069 /src/libnative | |
| parent | 04c446b4b66068a6742d29cd34326e520373f83e (diff) | |
| download | rust-962af9198f8f2a1e2d5121ac216b6f4d574ae54c.tar.gz rust-962af9198f8f2a1e2d5121ac216b6f4d574ae54c.zip | |
native: Protect against spurious wakeups on cvars
This is a very real problem with cvars on normal systems, and all of channels will not work if spurious wakeups are accepted. This problem is just solved with a synchronized flag (accessed in the cvar's lock) to see whether a signal() actually happened or whether it's spurious.
Diffstat (limited to 'src/libnative')
| -rw-r--r-- | src/libnative/task.rs | 42 |
1 files changed, 29 insertions, 13 deletions
diff --git a/src/libnative/task.rs b/src/libnative/task.rs index 0d5e08979ca..12e361d8041 100644 --- a/src/libnative/task.rs +++ b/src/libnative/task.rs @@ -33,6 +33,7 @@ pub fn new() -> ~Task { let mut task = ~Task::new(); task.put_runtime(~Ops { lock: unsafe { Mutex::new() }, + awoken: false, } as ~rt::Runtime); return task; } @@ -85,7 +86,8 @@ pub fn spawn_opts(opts: TaskOpts, f: proc()) { // This structure is the glue between channels and the 1:1 scheduling mode. This // structure is allocated once per task. struct Ops { - lock: Mutex, // native synchronization + lock: Mutex, // native synchronization + awoken: bool, // used to prevent spurious wakeups } impl rt::Runtime for Ops { @@ -139,9 +141,16 @@ impl rt::Runtime for Ops { // reasoning for this is the same logic as above in that the task silently // transfers ownership via the `uint`, not through normal compiler // semantics. + // + // On a mildly unrelated note, it should also be pointed out that OS + // condition variables are susceptible to spurious wakeups, which we need to + // be ready for. In order to accomodate for this fact, we have an extra + // `awoken` field which indicates whether we were actually woken up via some + // invocation of `reawaken`. This flag is only ever accessed inside the + // lock, so there's no need to make it atomic. fn deschedule(mut ~self, times: uint, mut cur_task: ~Task, f: |BlockedTask| -> Result<(), BlockedTask>) { - let my_lock: *mut Mutex = &mut self.lock as *mut Mutex; + let me = &mut *self as *mut Ops; cur_task.put_runtime(self as ~rt::Runtime); unsafe { @@ -149,15 +158,21 @@ impl rt::Runtime for Ops { let task = BlockedTask::block(cur_task); if times == 1 { - (*my_lock).lock(); + (*me).lock.lock(); + (*me).awoken = false; match f(task) { - Ok(()) => (*my_lock).wait(), + Ok(()) => { + while !(*me).awoken { + (*me).lock.wait(); + } + } Err(task) => { cast::forget(task.wake()); } } - (*my_lock).unlock(); + (*me).lock.unlock(); } else { let mut iter = task.make_selectable(times); - (*my_lock).lock(); + (*me).lock.lock(); + (*me).awoken = false; let success = iter.all(|task| { match f(task) { Ok(()) => true, @@ -167,10 +182,10 @@ impl rt::Runtime for Ops { } } }); - if success { - (*my_lock).wait(); + while success && !(*me).awoken { + (*me).lock.wait(); } - (*my_lock).unlock(); + (*me).lock.unlock(); } // re-acquire ownership of the task cur_task = cast::transmute::<uint, ~Task>(cur_task_dupe); @@ -184,12 +199,13 @@ impl rt::Runtime for Ops { // why it's valid to do so. fn reawaken(mut ~self, mut to_wake: ~Task, _can_resched: bool) { unsafe { - let lock: *mut Mutex = &mut self.lock as *mut Mutex; + let me = &mut *self as *mut Ops; to_wake.put_runtime(self as ~rt::Runtime); cast::forget(to_wake); - (*lock).lock(); - (*lock).signal(); - (*lock).unlock(); + (*me).lock.lock(); + (*me).awoken = true; + (*me).lock.signal(); + (*me).lock.unlock(); } } |
