Skip to content

Commit 1169a1f

Browse files
committed
Auto merge of #42604 - stepancheg:timedwait, r=alexcrichton
Fix condvar.wait(distant future) return immediately on OSX Fixes issue #37440: `pthread_cond_timedwait` on macOS Sierra seems to overflow `ts_sec` parameter and returns immediately. To work around this problem patch rounds timeout down to year 3000. Patch also fixes overflow when converting `u64` to `time_t`.
2 parents 3cb8034 + 0c26b59 commit 1169a1f

File tree

2 files changed

+81
-19
lines changed

2 files changed

+81
-19
lines changed

src/libstd/sync/condvar.rs

+51-15
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,10 @@ impl Drop for Condvar {
480480
mod tests {
481481
use sync::mpsc::channel;
482482
use sync::{Condvar, Mutex, Arc};
483+
use sync::atomic::{AtomicBool, Ordering};
483484
use thread;
484485
use time::Duration;
485-
use u32;
486+
use u64;
486487

487488
#[test]
488489
fn smoke() {
@@ -547,23 +548,58 @@ mod tests {
547548

548549
#[test]
549550
#[cfg_attr(target_os = "emscripten", ignore)]
550-
fn wait_timeout_ms() {
551+
fn wait_timeout_wait() {
551552
let m = Arc::new(Mutex::new(()));
552-
let m2 = m.clone();
553553
let c = Arc::new(Condvar::new());
554-
let c2 = c.clone();
555554

556-
let g = m.lock().unwrap();
557-
let (g, _no_timeout) = c.wait_timeout(g, Duration::from_millis(1)).unwrap();
558-
// spurious wakeups mean this isn't necessarily true
559-
// assert!(!no_timeout);
560-
let _t = thread::spawn(move || {
561-
let _g = m2.lock().unwrap();
562-
c2.notify_one();
563-
});
564-
let (g, timeout_res) = c.wait_timeout(g, Duration::from_millis(u32::MAX as u64)).unwrap();
565-
assert!(!timeout_res.timed_out());
566-
drop(g);
555+
loop {
556+
let g = m.lock().unwrap();
557+
let (_g, no_timeout) = c.wait_timeout(g, Duration::from_millis(1)).unwrap();
558+
// spurious wakeups mean this isn't necessarily true
559+
// so execute test again, if not timeout
560+
if !no_timeout.timed_out() {
561+
continue;
562+
}
563+
564+
break;
565+
}
566+
}
567+
568+
#[test]
569+
#[cfg_attr(target_os = "emscripten", ignore)]
570+
fn wait_timeout_wake() {
571+
let m = Arc::new(Mutex::new(()));
572+
let c = Arc::new(Condvar::new());
573+
574+
loop {
575+
let g = m.lock().unwrap();
576+
577+
let c2 = c.clone();
578+
let m2 = m.clone();
579+
580+
let notified = Arc::new(AtomicBool::new(false));
581+
let notified_copy = notified.clone();
582+
583+
let t = thread::spawn(move || {
584+
let _g = m2.lock().unwrap();
585+
thread::sleep(Duration::from_millis(1));
586+
notified_copy.store(true, Ordering::SeqCst);
587+
c2.notify_one();
588+
});
589+
let (g, timeout_res) = c.wait_timeout(g, Duration::from_millis(u64::MAX)).unwrap();
590+
assert!(!timeout_res.timed_out());
591+
// spurious wakeups mean this isn't necessarily true
592+
// so execute test again, if not notified
593+
if !notified.load(Ordering::SeqCst) {
594+
t.join().unwrap();
595+
continue;
596+
}
597+
drop(g);
598+
599+
t.join().unwrap();
600+
601+
break;
602+
}
567603
}
568604

569605
#[test]

src/libstd/sys/unix/condvar.rs

+30-4
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ const TIMESPEC_MAX: libc::timespec = libc::timespec {
2323
tv_nsec: 1_000_000_000 - 1,
2424
};
2525

26+
fn saturating_cast_to_time_t(value: u64) -> libc::time_t {
27+
if value > <libc::time_t>::max_value() as u64 {
28+
<libc::time_t>::max_value()
29+
} else {
30+
value as libc::time_t
31+
}
32+
}
33+
2634
impl Condvar {
2735
pub const fn new() -> Condvar {
2836
// Might be moved and address is changing it is better to avoid
@@ -79,8 +87,7 @@ impl Condvar {
7987

8088
// Nanosecond calculations can't overflow because both values are below 1e9.
8189
let nsec = dur.subsec_nanos() as libc::c_long + now.tv_nsec as libc::c_long;
82-
// FIXME: Casting u64 into time_t could truncate the value.
83-
let sec = (dur.as_secs() as libc::time_t)
90+
let sec = saturating_cast_to_time_t(dur.as_secs())
8491
.checked_add((nsec / 1_000_000_000) as libc::time_t)
8592
.and_then(|s| s.checked_add(now.tv_sec));
8693
let nsec = nsec % 1_000_000_000;
@@ -100,10 +107,29 @@ impl Condvar {
100107
// https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
101108
// https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
102109
#[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
103-
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
110+
pub unsafe fn wait_timeout(&self, mutex: &Mutex, mut dur: Duration) -> bool {
104111
use ptr;
105112
use time::Instant;
106113

114+
// 1000 years
115+
let max_dur = Duration::from_secs(1000 * 365 * 86400);
116+
117+
if dur > max_dur {
118+
// OSX implementation of `pthread_cond_timedwait` is buggy
119+
// with super long durations. When duration is greater than
120+
// 0x100_0000_0000_0000 seconds, `pthread_cond_timedwait`
121+
// in macOS Sierra return error 316.
122+
//
123+
// This program demonstrates the issue:
124+
// https://gist.github.com/stepancheg/198db4623a20aad2ad7cddb8fda4a63c
125+
//
126+
// To work around this issue, and possible bugs of other OSes, timeout
127+
// is clamped to 1000 years, which is allowable per the API of `wait_timeout`
128+
// because of spurious wakeups.
129+
130+
dur = max_dur;
131+
}
132+
107133
// First, figure out what time it currently is, in both system and
108134
// stable time. pthread_cond_timedwait uses system time, but we want to
109135
// report timeout based on stable time.
@@ -116,7 +142,7 @@ impl Condvar {
116142
(sys_now.tv_usec * 1000) as libc::c_long;
117143
let extra = (nsec / 1_000_000_000) as libc::time_t;
118144
let nsec = nsec % 1_000_000_000;
119-
let seconds = dur.as_secs() as libc::time_t;
145+
let seconds = saturating_cast_to_time_t(dur.as_secs());
120146

121147
let timeout = sys_now.tv_sec.checked_add(extra).and_then(|s| {
122148
s.checked_add(seconds)

0 commit comments

Comments
 (0)