@@ -27,8 +27,10 @@ use borrow::{to_uint};
27
27
use cell:: Cell ;
28
28
use rand:: { XorShiftRng , Rng , Rand } ;
29
29
use iter:: range;
30
+ use unstable:: mutex:: Mutex ;
30
31
use vec:: { OwnedVector } ;
31
32
33
+
32
34
/// A scheduler is responsible for coordinating the execution of Tasks
33
35
/// on a single thread. The scheduler runs inside a slightly modified
34
36
/// Rust Task. When not running this task is stored in the scheduler
@@ -618,6 +620,12 @@ impl Scheduler {
618
620
unsafe {
619
621
let task: * mut Task = Local :: unsafe_borrow ( ) ;
620
622
( * task) . sched . get_mut_ref ( ) . run_cleanup_job ( ) ;
623
+
624
+ // See the comments in switch_running_tasks_and_then for why a lock
625
+ // is acquired here. This is the resumption points and the "bounce"
626
+ // that it is referring to.
627
+ ( * task) . nasty_deschedule_lock . lock ( ) ;
628
+ ( * task) . nasty_deschedule_lock . unlock ( ) ;
621
629
}
622
630
}
623
631
@@ -671,6 +679,15 @@ impl Scheduler {
671
679
/// This passes a Scheduler pointer to the fn after the context switch
672
680
/// in order to prevent that fn from performing further scheduling operations.
673
681
/// Doing further scheduling could easily result in infinite recursion.
682
+ ///
683
+ /// Note that if the closure provided relinquishes ownership of the
684
+ /// BlockedTask, then it is possible for the task to resume execution before
685
+ /// the closure has finished executing. This would naturally introduce a
686
+ /// race if the closure and task shared portions of the environment.
687
+ ///
688
+ /// This situation is currently prevented, or in other words it is
689
+ /// guaranteed that this function will not return before the given closure
690
+ /// has returned.
674
691
pub fn deschedule_running_task_and_then ( mut ~self ,
675
692
f : |& mut Scheduler , BlockedTask |) {
676
693
// Trickier - we need to get the scheduler task out of self
@@ -682,10 +699,29 @@ impl Scheduler {
682
699
683
700
pub fn switch_running_tasks_and_then ( ~self , next_task : ~Task ,
684
701
f : |& mut Scheduler , BlockedTask |) {
685
- // This is where we convert the BlockedTask-taking closure into one
686
- // that takes just a Task
687
- self . change_task_context ( next_task, |sched, task| {
688
- f ( sched, BlockedTask :: block ( task) )
702
+ // And here comes one of the sad moments in which a lock is used in a
703
+ // core portion of the rust runtime. As always, this is highly
704
+ // undesirable, so there's a good reason behind it.
705
+ //
706
+ // There is an excellent outline of the problem in issue #8132, and it's
707
+ // summarized in that `f` is executed on a sched task, but its
708
+ // environment is on the previous task. If `f` relinquishes ownership of
709
+ // the BlockedTask, then it may introduce a race where `f` is using the
710
+ // environment as well as the code after the 'deschedule' block.
711
+ //
712
+ // The solution we have chosen to adopt for now is to acquire a
713
+ // task-local lock around this block. The resumption of the task in
714
+ // context switching will bounce on the lock, thereby waiting for this
715
+ // block to finish, eliminating the race mentioned above.
716
+ //
717
+ // To actually maintain a handle to the lock, we use an unsafe pointer
718
+ // to it, but we're guaranteed that the task won't exit until we've
719
+ // unlocked the lock so there's no worry of this memory going away.
720
+ self . change_task_context ( next_task, |sched, mut task| {
721
+ let lock: * mut Mutex = & mut task. nasty_deschedule_lock ;
722
+ unsafe { ( * lock) . lock ( ) }
723
+ f ( sched, BlockedTask :: block ( task) ) ;
724
+ unsafe { ( * lock) . unlock ( ) }
689
725
} )
690
726
}
691
727
0 commit comments