about summary refs log tree commit diff
path: root/src/libstd
diff options
context:
space:
mode:
authorbors <bors@rust-lang.org>2016-08-30 16:28:32 -0700
committerGitHub <noreply@github.com>2016-08-30 16:28:32 -0700
commiteac41469d778d18ae7bf38fa917ed0fe122f944b (patch)
tree566d38ce5971d64222c2d3cd00499e2d666e66a8 /src/libstd
parent4473130f4e0a20278225251ea6a0400258b03180 (diff)
parent59e5e0b2db2ab66f138044bde80c44c7f9391a00 (diff)
downloadrust-eac41469d778d18ae7bf38fa917ed0fe122f944b.tar.gz
rust-eac41469d778d18ae7bf38fa917ed0fe122f944b.zip
Auto merge of #35048 - tmiasko:monotonic-wait-timeout, r=alexcrichton
Use monotonic time in condition variables.

Configure condition variables to use monotonic time using
pthread_condattr_setclock on systems where this is possible.
This fixes the issue when thread waiting on condition variable is
woken up too late when system time is moved backwards.
Diffstat (limited to 'src/libstd')
-rw-r--r--src/libstd/sync/condvar.rs14
-rw-r--r--src/libstd/sys/common/condvar.rs7
-rw-r--r--src/libstd/sys/unix/condvar.rs67
-rw-r--r--src/libstd/sys/windows/condvar.rs3
4 files changed, 82 insertions, 9 deletions
diff --git a/src/libstd/sync/condvar.rs b/src/libstd/sync/condvar.rs
index 1f480f6d4a9..4c946e613ea 100644
--- a/src/libstd/sync/condvar.rs
+++ b/src/libstd/sync/condvar.rs
@@ -80,10 +80,14 @@ impl Condvar {
     /// notified.
     #[stable(feature = "rust1", since = "1.0.0")]
     pub fn new() -> Condvar {
-        Condvar {
+        let mut c = Condvar {
             inner: box sys::Condvar::new(),
             mutex: AtomicUsize::new(0),
+        };
+        unsafe {
+            c.inner.init();
         }
+        c
     }
 
     /// Blocks the current thread until this condition variable receives a
@@ -138,6 +142,10 @@ impl Condvar {
     /// differences that may not cause the maximum amount of time
     /// waited to be precisely `ms`.
     ///
+    /// Note that the best effort is made to ensure that the time waited is
+    /// measured with a monotonic clock, and not affected by the changes made to
+    /// the system time.
+    ///
     /// The returned boolean is `false` only if the timeout is known
     /// to have elapsed.
     ///
@@ -162,6 +170,10 @@ impl Condvar {
     /// preemption or platform differences that may not cause the maximum
     /// amount of time waited to be precisely `dur`.
     ///
+    /// Note that the best effort is made to ensure that the time waited is
+    /// measured with a monotonic clock, and not affected by the changes made to
+    /// the system time.
+    ///
     /// The returned `WaitTimeoutResult` value indicates if the timeout is
     /// known to have elapsed.
     ///
diff --git a/src/libstd/sys/common/condvar.rs b/src/libstd/sys/common/condvar.rs
index 33734a88cf3..b6f29dd5fc3 100644
--- a/src/libstd/sys/common/condvar.rs
+++ b/src/libstd/sys/common/condvar.rs
@@ -27,6 +27,13 @@ impl Condvar {
     /// first used with any of the functions below.
     pub const fn new() -> Condvar { Condvar(imp::Condvar::new()) }
 
+    /// Prepares the condition variable for use.
+    ///
+    /// This should be called once the condition variable is at a stable memory
+    /// address.
+    #[inline]
+    pub unsafe fn init(&mut self) { self.0.init() }
+
     /// Signals one waiter on this condition variable to wake up.
     #[inline]
     pub unsafe fn notify_one(&self) { self.0.notify_one() }
diff --git a/src/libstd/sys/unix/condvar.rs b/src/libstd/sys/unix/condvar.rs
index 2e1c1900b46..27b9f131d12 100644
--- a/src/libstd/sys/unix/condvar.rs
+++ b/src/libstd/sys/unix/condvar.rs
@@ -10,15 +10,19 @@
 
 use cell::UnsafeCell;
 use libc;
-use ptr;
 use sys::mutex::{self, Mutex};
-use time::{Instant, Duration};
+use time::Duration;
 
 pub struct Condvar { inner: UnsafeCell<libc::pthread_cond_t> }
 
 unsafe impl Send for Condvar {}
 unsafe impl Sync for Condvar {}
 
+const TIMESPEC_MAX: libc::timespec = libc::timespec {
+    tv_sec: <libc::time_t>::max_value(),
+    tv_nsec: 1_000_000_000 - 1,
+};
+
 impl Condvar {
     pub const fn new() -> Condvar {
         // Might be moved and address is changing it is better to avoid
@@ -26,6 +30,23 @@ impl Condvar {
         Condvar { inner: UnsafeCell::new(libc::PTHREAD_COND_INITIALIZER) }
     }
 
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
+    pub unsafe fn init(&mut self) {}
+
+    #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
+    pub unsafe fn init(&mut self) {
+        use mem;
+        let mut attr: libc::pthread_condattr_t = mem::uninitialized();
+        let r = libc::pthread_condattr_init(&mut attr);
+        assert_eq!(r, 0);
+        let r = libc::pthread_condattr_setclock(&mut attr, libc::CLOCK_MONOTONIC);
+        assert_eq!(r, 0);
+        let r = libc::pthread_cond_init(self.inner.get(), &attr);
+        assert_eq!(r, 0);
+        let r = libc::pthread_condattr_destroy(&mut attr);
+        assert_eq!(r, 0);
+    }
+
     #[inline]
     pub unsafe fn notify_one(&self) {
         let r = libc::pthread_cond_signal(self.inner.get());
@@ -44,10 +65,45 @@ impl Condvar {
         debug_assert_eq!(r, 0);
     }
 
+    // This implementation is used on systems that support pthread_condattr_setclock
+    // where we configure condition variable to use monotonic clock (instead of
+    // default system clock). This approach avoids all problems that result
+    // from changes made to the system time.
+    #[cfg(not(any(target_os = "macos", target_os = "ios", target_os = "android")))]
+    pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
+        use mem;
+
+        let mut now: libc::timespec = mem::zeroed();
+        let r = libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut now);
+        assert_eq!(r, 0);
+
+        // Nanosecond calculations can't overflow because both values are below 1e9.
+        let nsec = dur.subsec_nanos() as libc::c_long + now.tv_nsec as libc::c_long;
+        // FIXME: Casting u64 into time_t could truncate the value.
+        let sec = (dur.as_secs() as libc::time_t)
+            .checked_add((nsec / 1_000_000_000) as libc::time_t)
+            .and_then(|s| s.checked_add(now.tv_sec));
+        let nsec = nsec % 1_000_000_000;
+
+        let timeout = sec.map(|s| {
+            libc::timespec { tv_sec: s, tv_nsec: nsec }
+        }).unwrap_or(TIMESPEC_MAX);
+
+        let r = libc::pthread_cond_timedwait(self.inner.get(), mutex::raw(mutex),
+                                            &timeout);
+        assert!(r == libc::ETIMEDOUT || r == 0);
+        r == 0
+    }
+
+
     // This implementation is modeled after libcxx's condition_variable
     // https://github.com/llvm-mirror/libcxx/blob/release_35/src/condition_variable.cpp#L46
     // https://github.com/llvm-mirror/libcxx/blob/release_35/include/__mutex_base#L367
+    #[cfg(any(target_os = "macos", target_os = "ios", target_os = "android"))]
     pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
+        use ptr;
+        use time::Instant;
+
         // First, figure out what time it currently is, in both system and
         // stable time.  pthread_cond_timedwait uses system time, but we want to
         // report timeout based on stable time.
@@ -66,12 +122,7 @@ impl Condvar {
             s.checked_add(seconds)
         }).map(|s| {
             libc::timespec { tv_sec: s, tv_nsec: nsec }
-        }).unwrap_or_else(|| {
-            libc::timespec {
-                tv_sec: <libc::time_t>::max_value(),
-                tv_nsec: 1_000_000_000 - 1,
-            }
-        });
+        }).unwrap_or(TIMESPEC_MAX);
 
         // And wait!
         let r = libc::pthread_cond_timedwait(self.inner.get(), mutex::raw(mutex),
diff --git a/src/libstd/sys/windows/condvar.rs b/src/libstd/sys/windows/condvar.rs
index 8075374d42b..d708b327c55 100644
--- a/src/libstd/sys/windows/condvar.rs
+++ b/src/libstd/sys/windows/condvar.rs
@@ -25,6 +25,9 @@ impl Condvar {
     }
 
     #[inline]
+    pub unsafe fn init(&mut self) {}
+
+    #[inline]
     pub unsafe fn wait(&self, mutex: &Mutex) {
         let r = c::SleepConditionVariableSRW(self.inner.get(),
                                              mutex::raw(mutex),