Skip to content

Commit 929e80c

Browse files
committed
Yield from the main thread a few times at shutdown
1 parent 3162b7a commit 929e80c

File tree

1 file changed

+39
-5
lines changed

1 file changed

+39
-5
lines changed

src/concurrency/thread.rs

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,11 @@ pub struct ThreadId(u32);
4444
/// The main thread. When it terminates, the whole application terminates.
4545
const MAIN_THREAD: ThreadId = ThreadId(0);
4646

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+
4752
impl ThreadId {
4853
pub fn to_u32(self) -> u32 {
4954
self.0
@@ -276,6 +281,9 @@ pub struct ThreadManager<'mir, 'tcx> {
276281
yield_active_thread: bool,
277282
/// Callbacks that are called once the specified time passes.
278283
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,
279287
}
280288

281289
impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
@@ -290,6 +298,7 @@ impl<'mir, 'tcx> Default for ThreadManager<'mir, 'tcx> {
290298
thread_local_alloc_ids: Default::default(),
291299
yield_active_thread: false,
292300
timeout_callbacks: FxHashMap::default(),
301+
main_thread_yields_at_shutdown_remaining: MAIN_THREAD_YIELDS_AT_SHUTDOWN,
293302
}
294303
}
295304
}
@@ -302,6 +311,7 @@ impl VisitTags for ThreadManager<'_, '_> {
302311
timeout_callbacks,
303312
active_thread: _,
304313
yield_active_thread: _,
314+
main_thread_yields_at_shutdown_remaining: _,
305315
sync,
306316
} = self;
307317

@@ -620,11 +630,26 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
620630
/// long as we can and switch only when we have to (the active thread was
621631
/// blocked, terminated, or has explicitly asked to be preempted).
622632
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+
}
628653
}
629654
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
630655
if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
@@ -671,6 +696,15 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
671696
}
672697
}
673698
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+
674708
if self.threads[self.active_thread].state == ThreadState::Enabled {
675709
return Ok(SchedulingAction::ExecuteStep);
676710
}

0 commit comments

Comments
 (0)