about summary refs log tree commit diff
path: root/src/libnative
diff options
context:
space:
mode:
authorAlex Crichton <alex@alexcrichton.com>2013-12-15 22:19:34 -0800
committerAlex Crichton <alex@alexcrichton.com>2013-12-24 19:59:54 -0800
commit962af9198f8f2a1e2d5121ac216b6f4d574ae54c (patch)
treef990add092e17494929ab8c875254009f58ba069 /src/libnative
parent04c446b4b66068a6742d29cd34326e520373f83e (diff)
downloadrust-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.rs42
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();
         }
     }