@@ -44,6 +44,11 @@ pub struct ThreadId(u32);
44
44
/// The main thread. When it terminates, the whole application terminates.
45
45
const MAIN_THREAD : ThreadId = ThreadId ( 0 ) ;
46
46
47
+ /// When the main thread would exit, we will yield to any other thread that is ready to execute.
48
+ /// But we must only do that a finite number of times, or a background thread running `loop {}`
49
+ /// will hang the program.
50
+ const MAIN_THREAD_YIELDS_AT_SHUTDOWN : u64 = 1_000 ;
51
+
47
52
impl ThreadId {
48
53
pub fn to_u32 ( self ) -> u32 {
49
54
self . 0
@@ -276,6 +281,9 @@ pub struct ThreadManager<'mir, 'tcx> {
276
281
yield_active_thread : bool ,
277
282
/// Callbacks that are called once the specified time passes.
278
283
timeout_callbacks : FxHashMap < ThreadId , TimeoutCallbackInfo < ' mir , ' tcx > > ,
284
+ /// When the main thread is about to exit, we give other threads a few chances to finish up
285
+ /// whatever they are doing before we consider them leaked.
286
+ main_thread_yields_at_shutdown_remaining : u64 ,
279
287
}
280
288
281
289
impl < ' mir , ' tcx > Default for ThreadManager < ' mir , ' tcx > {
@@ -290,6 +298,7 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
290
298
thread_local_alloc_ids : Default :: default ( ) ,
291
299
yield_active_thread : false ,
292
300
timeout_callbacks : FxHashMap :: default ( ) ,
301
+ main_thread_yields_at_shutdown_remaining : MAIN_THREAD_YIELDS_AT_SHUTDOWN ,
293
302
}
294
303
}
295
304
}
@@ -302,6 +311,7 @@ impl VisitTags for ThreadManager<'_, '_> {
302
311
timeout_callbacks,
303
312
active_thread : _,
304
313
yield_active_thread : _,
314
+ main_thread_yields_at_shutdown_remaining : _,
305
315
sync,
306
316
} = self ;
307
317
@@ -620,11 +630,26 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
620
630
/// long as we can and switch only when we have to (the active thread was
621
631
/// blocked, terminated, or has explicitly asked to be preempted).
622
632
fn schedule ( & mut self , clock : & Clock ) -> InterpResult < ' tcx , SchedulingAction > {
623
- // Check whether the thread has **just** terminated (`check_terminated`
624
- // checks whether the thread has popped all its stack and if yes, sets
625
- // the thread state to terminated).
626
- if self . threads [ self . active_thread ] . check_terminated ( ) {
627
- return Ok ( SchedulingAction :: ExecuteDtors ) ;
633
+ // If we are the main thread, and our call stack is empty but our state is Enabled, we are
634
+ // about to terminate.
635
+ // But, if there are any other threads which can execute, yield to them instead of falling
636
+ // through to the termination state.
637
+ let active_thread = & self . threads [ self . active_thread ] ;
638
+ if self . active_thread == MAIN_THREAD
639
+ && active_thread. stack . is_empty ( )
640
+ && active_thread. state == ThreadState :: Enabled
641
+ && self . threads . iter ( ) . any ( |t| t. state == ThreadState :: Enabled && !t. stack . is_empty ( ) )
642
+ && self . main_thread_yields_at_shutdown_remaining > 0
643
+ {
644
+ self . yield_active_thread = true ;
645
+ self . main_thread_yields_at_shutdown_remaining -= 1 ;
646
+ } else {
647
+ // Check whether the thread has **just** terminated (`check_terminated`
648
+ // checks whether the thread has popped all its stack and if yes, sets
649
+ // the thread state to terminated).
650
+ if self . threads [ self . active_thread ] . check_terminated ( ) {
651
+ return Ok ( SchedulingAction :: ExecuteDtors ) ;
652
+ }
628
653
}
629
654
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
630
655
if self . threads [ MAIN_THREAD ] . state == ThreadState :: Terminated {
@@ -671,6 +696,15 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
671
696
}
672
697
}
673
698
self . yield_active_thread = false ;
699
+
700
+ // The main thread is special, it is allowed to yield when it is shutting down and has
701
+ // nothing to execute. So if another thread yields back to the main thread when it is in
702
+ // this partly-shut-down state, we make another pass through the scheduler to either yield
703
+ // back off them main thread, or enter its shutdown sequence.
704
+ if self . active_thread == MAIN_THREAD && self . threads [ self . active_thread ] . stack . is_empty ( ) {
705
+ return self . schedule ( clock) ;
706
+ }
707
+
674
708
if self . threads [ self . active_thread ] . state == ThreadState :: Enabled {
675
709
return Ok ( SchedulingAction :: ExecuteStep ) ;
676
710
}
0 commit comments