@@ -27,8 +27,10 @@ use borrow::{to_uint};
2727use cell:: Cell ;
2828use rand:: { XorShiftRng , Rng , Rand } ;
2929use iter:: range;
30+ use unstable:: mutex:: Mutex ;
3031use vec:: { OwnedVector } ;
3132
33+
3234/// A scheduler is responsible for coordinating the execution of Tasks
3335/// on a single thread. The scheduler runs inside a slightly modified
3436/// Rust Task. When not running this task is stored in the scheduler
@@ -618,6 +620,12 @@ impl Scheduler {
618620 unsafe {
619621 let task: * mut Task = Local :: unsafe_borrow ( ) ;
620622 ( * 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 ( ) ;
621629 }
622630 }
623631
@@ -671,6 +679,15 @@ impl Scheduler {
671679 /// This passes a Scheduler pointer to the fn after the context switch
672680 /// in order to prevent that fn from performing further scheduling operations.
673681 /// 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.
674691 pub fn deschedule_running_task_and_then ( mut ~self ,
675692 f : |& mut Scheduler , BlockedTask |) {
676693 // Trickier - we need to get the scheduler task out of self
@@ -682,10 +699,29 @@ impl Scheduler {
682699
683700 pub fn switch_running_tasks_and_then ( ~self , next_task : ~Task ,
684701 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 ( ) }
689725 } )
690726 }
691727
0 commit comments