Skip to content

Commit 7d296c4

Browse files
author
Vitali Lovich
committed
Add Condvar APIs not susceptible to spurious wake
Provide wait_until and wait_timeout_until helper wrappers that aren't susceptible to spurious wake.
1 parent 616b66d commit 7d296c4

File tree

1 file changed

+205
-2
lines changed

1 file changed

+205
-2
lines changed

src/libstd/sync/condvar.rs

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use sync::{mutex, MutexGuard, PoisonError};
1414
use sys_common::condvar as sys;
1515
use sys_common::mutex as sys_mutex;
1616
use sys_common::poison::{self, LockResult};
17-
use time::Duration;
17+
use time::{Duration, Instant};
1818

1919
/// A type indicating whether a timed wait on a condition variable returned
2020
/// due to a time out or not.
@@ -219,6 +219,61 @@ impl Condvar {
219219
}
220220
}
221221

222+
/// Blocks the current thread until this condition variable receives a
223+
/// notification and the required condition is met. There are no spurious
224+
/// wakeups when calling this.
225+
///
226+
/// This function will atomically unlock the mutex specified (represented by
227+
/// `guard`) and block the current thread. This means that any calls
228+
/// to [`notify_one`] or [`notify_all`] which happen logically after the
229+
/// mutex is unlocked are candidates to wake this thread up. When this
230+
/// function call returns, the lock specified will have been re-acquired.
231+
///
232+
/// # Errors
233+
///
234+
/// This function will return an error if the mutex being waited on is
235+
/// poisoned when this thread re-acquires the lock. For more information,
236+
/// see information about [poisoning] on the [`Mutex`] type.
237+
///
238+
/// [`notify_one`]: #method.notify_one
239+
/// [`notify_all`]: #method.notify_all
240+
/// [poisoning]: ../sync/struct.Mutex.html#poisoning
241+
/// [`Mutex`]: ../sync/struct.Mutex.html
242+
///
243+
/// # Examples
244+
///
245+
/// ```
246+
/// use std::sync::{Arc, Mutex, Condvar};
247+
/// use std::thread;
248+
///
249+
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
250+
/// let pair2 = pair.clone();
251+
///
252+
/// thread::spawn(move|| {
253+
/// let &(ref lock, ref cvar) = &*pair2;
254+
/// let mut started = lock.lock().unwrap();
255+
/// *started = true;
256+
/// // We notify the condvar that the value has changed.
257+
/// cvar.notify_one();
258+
/// });
259+
///
260+
/// // Wait for the thread to start up.
261+
/// let &(ref lock, ref cvar) = &*pair;
262+
/// // As long as the value inside the `Mutex` is false, we wait.
263+
/// cvar.wait_until(lock.lock().unwrap(), |ref started| { started });
264+
/// ```
265+
#[stable(feature = "wait_until", since = "1.24")]
266+
pub fn wait_until<'a, T, F>(&self, mut guard: MutexGuard<'a, T>,
267+
mut condition: F)
268+
-> LockResult<MutexGuard<'a, T>>
269+
where F: FnMut(&T) -> bool {
270+
while !condition(&*guard) {
271+
guard = self.wait(guard)?;
272+
}
273+
Ok(guard)
274+
}
275+
276+
222277
/// Waits on this condition variable for a notification, timing out after a
223278
/// specified duration.
224279
///
@@ -293,7 +348,15 @@ impl Condvar {
293348
///
294349
/// Note that the best effort is made to ensure that the time waited is
295350
/// measured with a monotonic clock, and not affected by the changes made to
296-
/// the system time.
351+
/// the system time. This function is susceptible to spurious wakeups.
352+
/// Condition variables normally have a boolean predicate associated with
353+
/// them, and the predicate must always be checked each time this function
354+
/// returns to protect against spurious wakeups. Additionally, it is
355+
/// typically desirable for the time-out to not exceed some duration in
356+
/// spite of spurious wakes, thus the sleep-duration is decremented by the
357+
/// amount slept. Alternatively, use the `wait_timeout_until` method
358+
/// to wait until a condition is met with a total time-out regardless
359+
/// of spurious wakes.
297360
///
298361
/// The returned [`WaitTimeoutResult`] value indicates if the timeout is
299362
/// known to have elapsed.
@@ -302,6 +365,7 @@ impl Condvar {
302365
/// returns, regardless of whether the timeout elapsed or not.
303366
///
304367
/// [`wait`]: #method.wait
368+
/// [`wait_timeout_until`]: #method.wait_timeout_until
305369
/// [`WaitTimeoutResult`]: struct.WaitTimeoutResult.html
306370
///
307371
/// # Examples
@@ -353,6 +417,76 @@ impl Condvar {
353417
}
354418
}
355419

420+
/// Waits on this condition variable for a notification, timing out after a
421+
/// specified duration.
422+
///
423+
/// The semantics of this function are equivalent to [`wait_until`] except
424+
/// that the thread will be blocked for roughly no longer than `dur`. This
425+
/// method should not be used for precise timing due to anomalies such as
426+
/// preemption or platform differences that may not cause the maximum
427+
/// amount of time waited to be precisely `dur`.
428+
///
429+
/// Note that the best effort is made to ensure that the time waited is
430+
/// measured with a monotonic clock, and not affected by the changes made to
431+
/// the system time.
432+
///
433+
/// The returned [`WaitTimeoutResult`] value indicates if the timeout is
434+
/// known to have elapsed without the condition being met.
435+
///
436+
/// Like [`wait_until`], the lock specified will be re-acquired when this
437+
/// function returns, regardless of whether the timeout elapsed or not.
438+
///
439+
/// [`wait_until`]: #method.wait_until
440+
/// [`wait_timeout`]: #method.wait_timeout
441+
/// [`WaitTimeoutResult`]: struct.WaitTimeoutResult.html
442+
///
443+
/// # Examples
444+
///
445+
/// ```
446+
/// use std::sync::{Arc, Mutex, Condvar};
447+
/// use std::thread;
448+
/// use std::time::Duration;
449+
///
450+
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
451+
/// let pair2 = pair.clone();
452+
///
453+
/// thread::spawn(move|| {
454+
/// let &(ref lock, ref cvar) = &*pair2;
455+
/// let mut started = lock.lock().unwrap();
456+
/// *started = true;
457+
/// // We notify the condvar that the value has changed.
458+
/// cvar.notify_one();
459+
/// });
460+
///
461+
/// // wait for the thread to start up
462+
/// let &(ref lock, ref cvar) = &*pair;
463+
/// let result = cvar.wait_timeout_until(lock, Duration::from_millis(100), |started| {
464+
/// started
465+
/// }).unwrap();
466+
/// if result.1.timed_out() {
467+
/// // timed-out without the condition ever evaluating to true.
468+
/// }
469+
/// // access the locked mutex via result.0
470+
/// ```
471+
#[stable(feature = "wait_timeout_until", since = "1.24")]
472+
pub fn wait_timeout_until<'a, T, F>(&self, mut guard: MutexGuard<'a, T>,
473+
mut dur: Duration, mut condition: F)
474+
-> LockResult<(MutexGuard<'a, T>, WaitTimeoutResult)>
475+
where F: FnMut(&T) -> bool {
476+
let timed_out = Duration::new(0, 0);
477+
loop {
478+
if !condition(&*guard) {
479+
return Ok((guard, WaitTimeoutResult(false)));
480+
} else if dur == timed_out {
481+
return Ok((guard, WaitTimeoutResult(false)));
482+
}
483+
let wait_timer = Instant::now();
484+
let wait_result = self.wait_timeout(guard, dur)?;
485+
dur = dur.checked_sub(wait_timer.elapsed()).unwrap_or(timed_out);
486+
guard = wait_result.0;
487+
}
488+
}
489+
356490
/// Wakes up one blocked thread on this condvar.
357491
///
358492
/// If there is a blocked thread on this condition variable, then it will
@@ -546,6 +680,29 @@ mod tests {
546680
}
547681
}
548682

683+
#[test]
684+
#[cfg_attr(target_os = "emscripten", ignore)]
685+
fn wait_until() {
686+
let pair = Arc::new((Mutex::new(false), Condvar::new()));
687+
let pair2 = pair.clone();
688+
689+
// Inside of our lock, spawn a new thread, and then wait for it to start.
690+
thread::spawn(move|| {
691+
let &(ref lock, ref cvar) = &*pair2;
692+
let mut started = lock.lock().unwrap();
693+
*started = true;
694+
// We notify the condvar that the value has changed.
695+
cvar.notify_one();
696+
});
697+
698+
// Wait for the thread to start up.
699+
let &(ref lock, ref cvar) = &*pair;
700+
let guard = cvar.wait_until(lock.lock().unwrap(), |started| {
701+
started
702+
});
703+
assert!(*guard);
704+
}
705+
549706
#[test]
550707
#[cfg_attr(target_os = "emscripten", ignore)]
551708
fn wait_timeout_wait() {
@@ -565,6 +722,52 @@ mod tests {
565722
}
566723
}
567724

725+
#[test]
726+
#[cfg_attr(target_os = "emscripten", ignore)]
727+
fn wait_timeout_until_wait() {
728+
let m = Arc::new(Mutex::new(()));
729+
let c = Arc::new(Condvar::new());
730+
731+
let g = m.lock().unwrap();
732+
let (_g, wait) = c.wait_timeout_until(g, Duration::from_millis(1), || { false }).unwrap();
733+
// no spurious wakeups. ensure it timed-out
734+
assert!(wait.timed_out());
735+
}
736+
737+
#[test]
738+
#[cfg_attr(target_os = "emscripten", ignore)]
739+
fn wait_timeout_until_instant_satisfy() {
740+
let m = Arc::new(Mutex::new(()));
741+
let c = Arc::new(Condvar::new());
742+
743+
let g = m.lock().unwrap();
744+
let (_g, wait) = c.wait_timeout_until(g, Duration::from_millis(0), || { true }).unwrap();
745+
// ensure it didn't time-out even if we were not given any time.
746+
assert!(!wait.timed_out());
747+
}
748+
749+
#[test]
750+
#[cfg_attr(target_os = "emscripten", ignore)]
751+
fn wait_timeout_until_wake() {
752+
let pair = Arc::new((Mutex::new(false), Condvar::new()));
753+
let pair_copy = pair.clone();
754+
755+
let g = m.lock().unwrap();
756+
let t = thread::spawn(move || {
757+
let &(ref lock, ref cvar) = &*pair2;
758+
let mut started = lock.lock().unwrap();
759+
thread::sleep(Duration::from_millis(1));
760+
started = true;
761+
cvar.notify_one();
762+
});
763+
let (g2, wait) = c.wait_timeout_until(g, Duration::from_millis(u64::MAX), |&notified| {
764+
notified
765+
}).unwrap();
766+
// ensure it didn't time-out even if we were not given any time.
767+
assert!(!wait.timed_out());
768+
assert!(*g2);
769+
}
770+
568771
#[test]
569772
#[cfg_attr(target_os = "emscripten", ignore)]
570773
fn wait_timeout_wake() {

0 commit comments

Comments
 (0)