@@ -26,7 +26,7 @@ use rt::local::Local;
26
26
use rt:: rtio:: { RemoteCallback , PausibleIdleCallback } ;
27
27
use borrow:: { to_uint} ;
28
28
use cell:: Cell ;
29
- use rand:: { XorShiftRng , Rng } ;
29
+ use rand:: { XorShiftRng , Rng , Rand } ;
30
30
use iter:: range;
31
31
use vec:: { OwnedVector } ;
32
32
@@ -78,7 +78,14 @@ pub struct Scheduler {
78
78
/// A fast XorShift rng for scheduler use
79
79
rng : XorShiftRng ,
80
80
/// A toggleable idle callback
81
- idle_callback : Option < ~PausibleIdleCallback >
81
+ idle_callback : Option < ~PausibleIdleCallback > ,
82
+ /// A countdown that starts at a random value and is decremented
83
+ /// every time a yield check is performed. When it hits 0 a task
84
+ /// will yield.
85
+ yield_check_count : uint ,
86
+ /// A flag to tell the scheduler loop it needs to do some stealing
87
+ /// in order to introduce randomness as part of a yield
88
+ steal_for_yield : bool
82
89
}
83
90
84
91
/// An indication of how hard to work on a given operation, the difference
@@ -89,6 +96,13 @@ enum EffortLevel {
89
96
GiveItYourBest
90
97
}
91
98
99
+ static MAX_YIELD_CHECKS : uint = 200 ;
100
+
101
+ fn reset_yield_check ( rng : & mut XorShiftRng ) -> uint {
102
+ let r: uint = Rand :: rand ( rng) ;
103
+ r % MAX_YIELD_CHECKS + 1
104
+ }
105
+
92
106
impl Scheduler {
93
107
94
108
// * Initialization Functions
@@ -113,7 +127,7 @@ impl Scheduler {
113
127
friend : Option < SchedHandle > )
114
128
-> Scheduler {
115
129
116
- Scheduler {
130
+ let mut sched = Scheduler {
117
131
sleeper_list : sleeper_list,
118
132
message_queue : MessageQueue :: new ( ) ,
119
133
sleepy : false ,
@@ -127,8 +141,14 @@ impl Scheduler {
127
141
run_anything : run_anything,
128
142
friend_handle : friend,
129
143
rng : XorShiftRng :: new ( ) ,
130
- idle_callback : None
131
- }
144
+ idle_callback : None ,
145
+ yield_check_count : 0 ,
146
+ steal_for_yield : false
147
+ } ;
148
+
149
+ sched. yield_check_count = reset_yield_check ( & mut sched. rng ) ;
150
+
151
+ return sched;
132
152
}
133
153
134
154
// XXX: This may eventually need to be refactored so that
@@ -307,8 +327,7 @@ impl Scheduler {
307
327
}
308
328
Some ( TaskFromFriend ( task) ) => {
309
329
rtdebug ! ( "got a task from a friend. lovely!" ) ;
310
- this. process_task ( task,
311
- Scheduler :: resume_task_immediately_cl) . map_move ( Local :: put) ;
330
+ this. process_task ( task, Scheduler :: resume_task_immediately_cl) ;
312
331
return None ;
313
332
}
314
333
Some ( Wake ) => {
@@ -352,8 +371,8 @@ impl Scheduler {
352
371
match this. find_work ( ) {
353
372
Some ( task) => {
354
373
rtdebug ! ( "found some work! processing the task" ) ;
355
- return this. process_task ( task,
356
- Scheduler :: resume_task_immediately_cl ) ;
374
+ this. process_task ( task, Scheduler :: resume_task_immediately_cl ) ;
375
+ return None ;
357
376
}
358
377
None => {
359
378
rtdebug ! ( "no work was found, returning the scheduler struct" ) ;
@@ -373,14 +392,35 @@ impl Scheduler {
373
392
// there, trying to steal from the remote work queues.
374
393
fn find_work ( & mut self ) -> Option < ~Task > {
375
394
rtdebug ! ( "scheduler looking for work" ) ;
376
- match self . work_queue . pop ( ) {
377
- Some ( task) => {
378
- rtdebug ! ( "found a task locally" ) ;
379
- return Some ( task)
395
+ if !self . steal_for_yield {
396
+ match self . work_queue . pop ( ) {
397
+ Some ( task) => {
398
+ rtdebug ! ( "found a task locally" ) ;
399
+ return Some ( task)
400
+ }
401
+ None => {
402
+ rtdebug ! ( "scheduler trying to steal" ) ;
403
+ return self . try_steals ( ) ;
404
+ }
380
405
}
381
- None => {
382
- rtdebug ! ( "scheduler trying to steal" ) ;
383
- return self . try_steals ( ) ;
406
+ } else {
407
+ // During execution of the last task, it performed a 'yield',
408
+ // so we're doing some work stealing in order to introduce some
409
+ // scheduling randomness. Otherwise we would just end up popping
410
+ // that same task again. This is pretty lame and is to work around
411
+ // the problem that work stealing is not designed for 'non-strict'
412
+ // (non-fork-join) task parallelism.
413
+ self . steal_for_yield = false ;
414
+ match self . try_steals ( ) {
415
+ Some ( task) => {
416
+ rtdebug ! ( "stole a task after yielding" ) ;
417
+ return Some ( task) ;
418
+ }
419
+ None => {
420
+ rtdebug ! ( "did not steal a task after yielding" ) ;
421
+ // Back to business
422
+ return self . find_work ( ) ;
423
+ }
384
424
}
385
425
}
386
426
}
@@ -409,7 +449,7 @@ impl Scheduler {
409
449
// place.
410
450
411
451
fn process_task ( ~self , task : ~Task ,
412
- schedule_fn : SchedulingFn ) -> Option < ~ Scheduler > {
452
+ schedule_fn : SchedulingFn ) {
413
453
let mut this = self ;
414
454
let mut task = task;
415
455
@@ -422,23 +462,23 @@ impl Scheduler {
422
462
rtdebug ! ( "sending task home" ) ;
423
463
task. give_home ( Sched ( home_handle) ) ;
424
464
Scheduler :: send_task_home ( task) ;
425
- return Some ( this) ;
465
+ Local :: put ( this) ;
426
466
} else {
427
467
rtdebug ! ( "running task here" ) ;
428
468
task. give_home ( Sched ( home_handle) ) ;
429
- return schedule_fn ( this, task) ;
469
+ schedule_fn ( this, task) ;
430
470
}
431
471
}
432
472
AnySched if this. run_anything => {
433
473
rtdebug ! ( "running anysched task here" ) ;
434
474
task. give_home ( AnySched ) ;
435
- return schedule_fn ( this, task) ;
475
+ schedule_fn ( this, task) ;
436
476
}
437
477
AnySched => {
438
478
rtdebug ! ( "sending task to friend" ) ;
439
479
task. give_home ( AnySched ) ;
440
480
this. send_to_friend ( task) ;
441
- return Some ( this) ;
481
+ Local :: put ( this) ;
442
482
}
443
483
}
444
484
}
@@ -607,15 +647,14 @@ impl Scheduler {
607
647
608
648
// * Context Swapping Helpers - Here be ugliness!
609
649
610
- pub fn resume_task_immediately ( ~self , task : ~Task ) -> Option < ~ Scheduler > {
650
+ pub fn resume_task_immediately ( ~self , task : ~Task ) {
611
651
do self . change_task_context ( task) |sched, stask| {
612
652
sched. sched_task = Some ( stask) ;
613
653
}
614
- return None ;
615
654
}
616
655
617
656
fn resume_task_immediately_cl ( sched : ~Scheduler ,
618
- task : ~Task ) -> Option < ~ Scheduler > {
657
+ task : ~Task ) {
619
658
sched. resume_task_immediately ( task)
620
659
}
621
660
@@ -662,11 +701,10 @@ impl Scheduler {
662
701
}
663
702
}
664
703
665
- fn switch_task ( sched : ~Scheduler , task : ~Task ) -> Option < ~ Scheduler > {
704
+ fn switch_task ( sched : ~Scheduler , task : ~Task ) {
666
705
do sched. switch_running_tasks_and_then ( task) |sched, last_task| {
667
706
sched. enqueue_blocked_task ( last_task) ;
668
707
} ;
669
- return None ;
670
708
}
671
709
672
710
// * Task Context Helpers
@@ -686,7 +724,7 @@ impl Scheduler {
686
724
687
725
pub fn run_task ( task : ~Task ) {
688
726
let sched: ~Scheduler = Local :: take ( ) ;
689
- sched. process_task ( task, Scheduler :: switch_task) . map_move ( Local :: put ) ;
727
+ sched. process_task ( task, Scheduler :: switch_task) ;
690
728
}
691
729
692
730
pub fn run_task_later ( next_task : ~Task ) {
@@ -696,6 +734,33 @@ impl Scheduler {
696
734
} ;
697
735
}
698
736
737
+ /// Yield control to the scheduler, executing another task. This is guaranteed
738
+ /// to introduce some amount of randomness to the scheduler. Currently the
739
+ /// randomness is a result of performing a round of work stealing (which
740
+ /// may end up stealing from the current scheduler).
741
+ pub fn yield_now ( ~self ) {
742
+ let mut this = self ;
743
+ this. yield_check_count = reset_yield_check ( & mut this. rng ) ;
744
+ // Tell the scheduler to start stealing on the next iteration
745
+ this. steal_for_yield = true ;
746
+ do this. deschedule_running_task_and_then |sched, task| {
747
+ sched. enqueue_blocked_task ( task) ;
748
+ }
749
+ }
750
+
751
+ pub fn maybe_yield ( ~self ) {
752
+ // The number of times to do the yield check before yielding, chosen arbitrarily.
753
+ let mut this = self ;
754
+ rtassert ! ( this. yield_check_count > 0 ) ;
755
+ this. yield_check_count -= 1 ;
756
+ if this. yield_check_count == 0 {
757
+ this. yield_now ( ) ;
758
+ } else {
759
+ Local :: put ( this) ;
760
+ }
761
+ }
762
+
763
+
699
764
// * Utility Functions
700
765
701
766
pub fn sched_id ( & self ) -> uint { to_uint ( self ) }
@@ -718,7 +783,7 @@ impl Scheduler {
718
783
719
784
// Supporting types
720
785
721
- type SchedulingFn = ~fn ( ~Scheduler , ~Task ) -> Option < ~ Scheduler > ;
786
+ type SchedulingFn = ~fn ( ~Scheduler , ~Task ) ;
722
787
723
788
pub enum SchedMessage {
724
789
Wake ,
@@ -1231,4 +1296,40 @@ mod test {
1231
1296
}
1232
1297
}
1233
1298
}
1299
+
1300
+ #[ test]
1301
+ fn dont_starve_2 ( ) {
1302
+ use rt:: comm:: oneshot;
1303
+
1304
+ do stress_factor ( ) . times {
1305
+ do run_in_newsched_task {
1306
+ let ( port, chan) = oneshot ( ) ;
1307
+ let ( _port2, chan2) = stream ( ) ;
1308
+
1309
+ // This task should not be able to starve the other task.
1310
+ // The sends should eventually yield.
1311
+ do spawntask {
1312
+ while !port. peek ( ) {
1313
+ chan2. send ( ( ) ) ;
1314
+ }
1315
+ }
1316
+
1317
+ chan. send ( ( ) ) ;
1318
+ }
1319
+ }
1320
+ }
1321
+
1322
+ // Regression test for a logic bug that would cause single-threaded schedulers
1323
+ // to sleep forever after yielding and stealing another task.
1324
+ #[ test]
1325
+ fn single_threaded_yield ( ) {
1326
+ use task:: { spawn, spawn_sched, SingleThreaded , deschedule} ;
1327
+ use num:: Times ;
1328
+
1329
+ do spawn_sched ( SingleThreaded ) {
1330
+ do 5 . times { deschedule ( ) ; }
1331
+ }
1332
+ do spawn { }
1333
+ do spawn { }
1334
+ }
1234
1335
}
0 commit comments