Skip to content

Commit 440311e

Browse files
committed
Implement Unparker::unparker_with_result()
This method is like `Unpark::unpark()`, but also returns the previous state of the token. This change also introduces one new public enum `UnparkResult`.
1 parent 983d56b commit 440311e

File tree

3 files changed

+84
-7
lines changed

3 files changed

+84
-7
lines changed

crossbeam-utils/src/sync/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ mod parker;
1111
mod sharded_lock;
1212
mod wait_group;
1313

14-
pub use self::parker::{Parker, UnparkReason, Unparker};
14+
pub use self::parker::{Parker, UnparkReason, UnparkResult, Unparker};
1515
#[cfg(not(crossbeam_loom))]
1616
pub use self::sharded_lock::{ShardedLock, ShardedLockReadGuard, ShardedLockWriteGuard};
1717
pub use self::wait_group::WaitGroup;

crossbeam-utils/src/sync/parker.rs

Lines changed: 55 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,41 @@ impl Unparker {
255255
/// [`park`]: Parker::park
256256
/// [`park_timeout`]: Parker::park_timeout
257257
pub fn unpark(&self) {
258+
self.inner.unpark();
259+
}
260+
261+
/// Atomically makes the token available if it is not already, returning an [`UnparkResult`]
262+
/// indicating the result of the unpark operation.
263+
///
264+
/// This method will wake up the thread blocked on [`park`] or [`park_timeout`], if there is
265+
/// any.
266+
///
267+
/// # Examples
268+
///
269+
/// ```
270+
/// use std::thread;
271+
/// use std::time::Duration;
272+
/// use crossbeam_utils::sync::{Parker, UnparkResult};
273+
///
274+
/// let p = Parker::new();
275+
/// let u = p.unparker().clone();
276+
///
277+
/// let result = u.unpark_with_result();
278+
/// assert_eq!(result, UnparkResult::NotParked);
279+
/// p.park(); // consume the token and immediately return
280+
///
281+
/// # let t =
282+
/// thread::spawn(move || {
283+
/// thread::sleep(Duration::from_millis(500));
284+
/// let result = u.unpark_with_result();
285+
/// assert_eq!(result, UnparkResult::Notified);
286+
/// });
287+
///
288+
/// // Wakes up when `u.unpark()` provides the token.
289+
/// p.park();
290+
/// # t.join().unwrap(); // join thread to avoid https://github.com/rust-lang/miri/issues/1371
291+
/// ```
292+
pub fn unpark_with_result(&self) -> UnparkResult {
258293
self.inner.unpark()
259294
}
260295

@@ -324,6 +359,21 @@ pub enum UnparkReason {
324359
Timeout,
325360
}
326361

362+
/// An enum that reports the result of an `Unparker::unpark` call. This includes whether the parker
363+
/// was parked when `unpark` was called, and if so, whether it was already notified by a previous
364+
/// `unpark` call.
365+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
366+
pub enum UnparkResult {
367+
/// The parker was not parked when `unpark` was called.
368+
NotParked,
369+
370+
/// The parker was parked, but was already notified by an earlier `unpark` call.
371+
AlreadyNotified,
372+
373+
/// The parker was parked and has been successfully notified by this `unpark` call.
374+
Notified,
375+
}
376+
327377
const EMPTY: usize = 0;
328378
const PARKED: usize = 1;
329379
const NOTIFIED: usize = 2;
@@ -407,15 +457,15 @@ impl Inner {
407457
}
408458
}
409459

410-
pub(crate) fn unpark(&self) {
460+
pub(crate) fn unpark(&self) -> UnparkResult {
411461
// To ensure the unparked thread will observe any writes we made before this call, we must
412462
// perform a release operation that `park` can synchronize with. To do that we must write
413463
// `NOTIFIED` even if `state` is already `NOTIFIED`. That is why this must be a swap rather
414464
// than a compare-and-swap that returns if it reads `NOTIFIED` on failure.
415465
match self.state.swap(NOTIFIED, SeqCst) {
416-
EMPTY => return, // no one was waiting
417-
NOTIFIED => return, // already unparked
418-
PARKED => {} // gotta go wake someone up
466+
EMPTY => return UnparkResult::NotParked, // no one was waiting
467+
NOTIFIED => return UnparkResult::AlreadyNotified, // already unparked
468+
PARKED => {} // gotta go wake someone up
419469
_ => panic!("inconsistent state in unpark"),
420470
}
421471

@@ -429,5 +479,6 @@ impl Inner {
429479
// it doesn't get woken only to have to wait for us to release `lock`.
430480
drop(self.lock.lock().unwrap());
431481
self.cvar.notify_one();
482+
UnparkResult::Notified
432483
}
433484
}

crossbeam-utils/tests/parker.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use std::thread::sleep;
1+
use std::thread::{sleep, spawn};
22
use std::time::Duration;
33

4-
use crossbeam_utils::sync::{Parker, UnparkReason};
4+
use crossbeam_utils::sync::{Parker, UnparkReason, UnparkResult};
55
use crossbeam_utils::thread;
66

77
#[test]
@@ -47,3 +47,29 @@ fn park_timeout_unpark_called_other_thread() {
4747
.unwrap();
4848
}
4949
}
50+
51+
#[test]
52+
fn unpark_with_result_called_before_park() {
53+
let p = Parker::new();
54+
let u = p.unparker().clone();
55+
56+
let result = u.unpark_with_result();
57+
assert_eq!(result, UnparkResult::NotParked);
58+
59+
p.park(); // consume the token and immediately return
60+
}
61+
62+
#[test]
63+
fn unpark_with_result_called_after_park() {
64+
let p = Parker::new();
65+
let u = p.unparker().clone();
66+
67+
let t = spawn(move || {
68+
sleep(Duration::from_millis(500));
69+
let result = u.unpark_with_result();
70+
assert_eq!(result, UnparkResult::Notified);
71+
});
72+
73+
p.park(); // consume the token and immediately return
74+
t.join().unwrap();
75+
}

0 commit comments

Comments
 (0)