Skip to content

Commit e253224

Browse files
authored
Rollup merge of rust-lang#47970 - vlovich:condvar_wait_until, r=dtolnay
Add Condvar APIs not susceptible to spurious wake Provide wait_until and wait_timeout_until helper wrappers that aren't susceptible to spurious wake. Additionally wait_timeout_until makes it possible to more easily write code that waits for a fixed amount of time in face of spurious wakes since otherwise each user would have to do math on adjusting the duration. Implements rust-lang#47960.
2 parents f5116e7 + 14b403c commit e253224

File tree

1 file changed

+214
-2
lines changed

1 file changed

+214
-2
lines changed

src/libstd/sync/condvar.rs

+214-2
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.
@@ -221,6 +221,64 @@ impl Condvar {
221221
}
222222
}
223223

224+
/// Blocks the current thread until this condition variable receives a
225+
/// notification and the required condition is met. Spurious wakeups are
226+
/// ignored and this function will only return once the condition has been
227+
/// met.
228+
///
229+
/// This function will atomically unlock the mutex specified (represented by
230+
/// `guard`) and block the current thread. This means that any calls
231+
/// to [`notify_one`] or [`notify_all`] which happen logically after the
232+
/// mutex is unlocked are candidates to wake this thread up. When this
233+
/// function call returns, the lock specified will have been re-acquired.
234+
///
235+
/// # Errors
236+
///
237+
/// This function will return an error if the mutex being waited on is
238+
/// poisoned when this thread re-acquires the lock. For more information,
239+
/// see information about [poisoning] on the [`Mutex`] type.
240+
///
241+
/// [`notify_one`]: #method.notify_one
242+
/// [`notify_all`]: #method.notify_all
243+
/// [poisoning]: ../sync/struct.Mutex.html#poisoning
244+
/// [`Mutex`]: ../sync/struct.Mutex.html
245+
///
246+
/// # Examples
247+
///
248+
/// ```
249+
/// #![feature(wait_until)]
250+
///
251+
/// use std::sync::{Arc, Mutex, Condvar};
252+
/// use std::thread;
253+
///
254+
/// let pair = Arc::new((Mutex::new(false), Condvar::new()));
255+
/// let pair2 = pair.clone();
256+
///
257+
/// thread::spawn(move|| {
258+
/// let &(ref lock, ref cvar) = &*pair2;
259+
/// let mut started = lock.lock().unwrap();
260+
/// *started = true;
261+
/// // We notify the condvar that the value has changed.
262+
/// cvar.notify_one();
263+
/// });
264+
///
265+
/// // Wait for the thread to start up.
266+
/// let &(ref lock, ref cvar) = &*pair;
267+
/// // As long as the value inside the `Mutex` is false, we wait.
268+
/// let _guard = cvar.wait_until(lock.lock().unwrap(), |started| { *started }).unwrap();
269+
/// ```
270+
#[unstable(feature = "wait_until", issue = "47960")]
271+
pub fn wait_until<'a, T, F>(&self, mut guard: MutexGuard<'a, T>,
272+
mut condition: F)
273+
-> LockResult<MutexGuard<'a, T>>
274+
where F: FnMut(&mut T) -> bool {
275+
while !condition(&mut *guard) {
276+
guard = self.wait(guard)?;
277+
}
278+
Ok(guard)
279+
}
280+
281+
224282
/// Waits on this condition variable for a notification, timing out after a
225283
/// specified duration.
226284
///
@@ -295,7 +353,15 @@ impl Condvar {
295353
///
296354
/// Note that the best effort is made to ensure that the time waited is
297355
/// measured with a monotonic clock, and not affected by the changes made to
298-
/// the system time.
356+
/// the system time. This function is susceptible to spurious wakeups.
357+
/// Condition variables normally have a boolean predicate associated with
358+
/// them, and the predicate must always be checked each time this function
359+
/// returns to protect against spurious wakeups. Additionally, it is
360+
/// typically desirable for the time-out to not exceed some duration in
361+
/// spite of spurious wakes, thus the sleep-duration is decremented by the
362+
/// amount slept. Alternatively, use the `wait_timeout_until` method
363+
/// to wait until a condition is met with a total time-out regardless
364+
/// of spurious wakes.
299365
///
300366
/// The returned [`WaitTimeoutResult`] value indicates if the timeout is
301367
/// known to have elapsed.
@@ -304,6 +370,7 @@ impl Condvar {
304370
/// returns, regardless of whether the timeout elapsed or not.
305371
///
306372
/// [`wait`]: #method.wait
373+
/// [`wait_timeout_until`]: #method.wait_timeout_until
307374
/// [`WaitTimeoutResult`]: struct.WaitTimeoutResult.html
308375
///
309376
/// # Examples
@@ -355,6 +422,80 @@ impl Condvar {
355422
}
356423
}
357424

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

481622
#[cfg(test)]
482623
mod tests {
624+
/// #![feature(wait_until)]
483625
use sync::mpsc::channel;
484626
use sync::{Condvar, Mutex, Arc};
485627
use sync::atomic::{AtomicBool, Ordering};
@@ -548,6 +690,29 @@ mod tests {
548690
}
549691
}
550692

693+
#[test]
694+
#[cfg_attr(target_os = "emscripten", ignore)]
695+
fn wait_until() {
696+
let pair = Arc::new((Mutex::new(false), Condvar::new()));
697+
let pair2 = pair.clone();
698+
699+
// Inside of our lock, spawn a new thread, and then wait for it to start.
700+
thread::spawn(move|| {
701+
let &(ref lock, ref cvar) = &*pair2;
702+
let mut started = lock.lock().unwrap();
703+
*started = true;
704+
// We notify the condvar that the value has changed.
705+
cvar.notify_one();
706+
});
707+
708+
// Wait for the thread to start up.
709+
let &(ref lock, ref cvar) = &*pair;
710+
let guard = cvar.wait_until(lock.lock().unwrap(), |started| {
711+
*started
712+
});
713+
assert!(*guard.unwrap());
714+
}
715+
551716
#[test]
552717
#[cfg_attr(target_os = "emscripten", ignore)]
553718
fn wait_timeout_wait() {
@@ -567,6 +732,53 @@ mod tests {
567732
}
568733
}
569734

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

0 commit comments

Comments
 (0)