Auto merge of #1772 - RalfJung:less-timeout-checking, r=RalfJung

only check timeouts when a thread yields

Currently, we check for expired timeouts after each step of execution. That seems excessive. This changes the scheduler to only check for timeouts when the active thread cannot continue running any more.

`@vakaras` does this sound right? `pthread_cond_timedwait` anyway already yields, of course, since it blocks on getting the signal (or the timeout).
This commit is contained in:
bors 2021-04-11 14:59:27 +00:00
commit e6ffc689aa
2 changed files with 20 additions and 14 deletions

View File

@ -477,6 +477,7 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
if self.threads[self.active_thread].check_terminated() {
return Ok(SchedulingAction::ExecuteDtors);
}
// If we get here again and the thread is *still* terminated, there are no more dtors to run.
if self.threads[MAIN_THREAD].state == ThreadState::Terminated {
// The main thread terminated; stop the program.
if self.threads.iter().any(|thread| thread.state != ThreadState::Terminated) {
@ -490,26 +491,25 @@ impl<'mir, 'tcx: 'mir> ThreadManager<'mir, 'tcx> {
}
return Ok(SchedulingAction::Stop);
}
// At least for `pthread_cond_timedwait` we need to report timeout when
// the function is called already after the specified time even if a
// signal is received before the thread gets scheduled. Therefore, we
// need to schedule all timeout callbacks before we continue regular
// execution.
//
// Documentation:
// https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html#
let potential_sleep_time =
self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min();
if potential_sleep_time == Some(Duration::new(0, 0)) {
return Ok(SchedulingAction::ExecuteTimeoutCallback);
}
// No callbacks scheduled, pick a regular thread to execute.
// This thread and the program can keep going.
if self.threads[self.active_thread].state == ThreadState::Enabled
&& !self.yield_active_thread
{
// The currently active thread is still enabled, just continue with it.
return Ok(SchedulingAction::ExecuteStep);
}
// The active thread yielded. Let's see if there are any timeouts to take care of. We do
// this *before* running any other thread, to ensure that timeouts "in the past" fire before
// any other thread can take an action. This ensures that for `pthread_cond_timedwait`, "an
// error is returned if [...] the absolute time specified by abstime has already been passed
// at the time of the call".
// <https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html>
let potential_sleep_time =
self.timeout_callbacks.values().map(|info| info.call_time.get_wait_time()).min();
if potential_sleep_time == Some(Duration::new(0, 0)) {
return Ok(SchedulingAction::ExecuteTimeoutCallback);
}
// No callbacks scheduled, pick a regular thread to execute.
// We need to pick a new thread for execution.
for (id, thread) in self.threads.iter_enumerated() {
if thread.state == ThreadState::Enabled {

View File

@ -38,6 +38,12 @@ fn test_timed_wait_timeout(clock_id: i32) {
let elapsed_time = current_time.elapsed().as_millis();
assert!(900 <= elapsed_time && elapsed_time <= 1300);
// Test calling `pthread_cond_timedwait` again with an already elapsed timeout.
assert_eq!(
libc::pthread_cond_timedwait(&mut cond as *mut _, &mut mutex as *mut _, &timeout),
libc::ETIMEDOUT
);
// Test that invalid nanosecond values (above 10^9 or negative) are rejected with the
// correct error code.
let invalid_timeout_1 = libc::timespec { tv_sec: now.tv_sec + 1, tv_nsec: 1_000_000_000 };