diff --git a/src/thread.rs b/src/thread.rs index 0d4fcbd49ff..8b1787132cb 100644 --- a/src/thread.rs +++ b/src/thread.rs @@ -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". + // + 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 { diff --git a/tests/run-pass/concurrency/libc_pthread_cond.rs b/tests/run-pass/concurrency/libc_pthread_cond.rs index d4e52bb3a97..a545c922db1 100644 --- a/tests/run-pass/concurrency/libc_pthread_cond.rs +++ b/tests/run-pass/concurrency/libc_pthread_cond.rs @@ -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 };