Skip to content

Commit

Permalink
Implement Arc::unwrap_or_drop.
Browse files Browse the repository at this point in the history
Also add documentation and tests for it.
This commit has some minor unresolved questions and is intended to be amended.
  • Loading branch information
steffahn committed Aug 26, 2020
1 parent ee54128 commit a534492
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 0 deletions.
56 changes: 56 additions & 0 deletions library/alloc/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,62 @@ impl<T> Arc<T> {
Ok(elem)
}
}

/// Returns the inner value, if the `Arc` has exactly one strong reference.
///
/// Otherwise, [`None`] is returned and the `Arc` is dropped.
///
/// This will succeed even if there are outstanding weak references.
///
/// If `unwrap_or_drop` is called on every clone of this `Arc`,
/// it is guaranteed that exactly one of the calls returns the inner value.
/// The similar expression `Arc::try_unwrap(this).ok()` does not
/// offer this guarantee.
///
/// # Examples
///
/// ```
/// #![feature(unwrap_or_drop)]
///
/// use std::sync::Arc;
///
/// let x = Arc::new(3);
/// let y = Arc::clone(&x);
///
/// let x_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(x));
/// let y_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(y));
///
/// let x_unwrapped_value = x_unwrap_thread.join().unwrap();
/// let y_unwrapped_value = y_unwrap_thread.join().unwrap();
///
/// assert!(matches!(
/// (x_unwrapped_value, y_unwrapped_value),
/// (None, Some(3)) | (Some(3), None)
/// ));
/// ```
#[inline]
#[unstable(feature = "unwrap_or_drop", issue = "none")] // FIXME: add issue
// FIXME: should this copy all/some of the comments from drop and drop_slow?
pub fn unwrap_or_drop(this: Self) -> Option<T> {
// following the implementation of `drop` (and `drop_slow`)
let mut this = core::mem::ManuallyDrop::new(this);

if this.inner().strong.fetch_sub(1, Release) != 1 {
return None;
}

acquire!(this.inner().strong);

// FIXME: should the part below this be moved into a seperate #[inline(never)]
// function, like it's done with drop_slow in drop?

// using `ptr::read` where `drop_slow` was using `ptr::drop_in_place`
let inner = unsafe { ptr::read(Self::get_mut_unchecked(&mut this)) };

drop(Weak { ptr: this.ptr });

Some(inner)
}
}

impl<T> Arc<[T]> {
Expand Down
39 changes: 39 additions & 0 deletions library/alloc/src/sync/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,45 @@ fn try_unwrap() {
assert_eq!(Arc::try_unwrap(x), Ok(5));
}

#[test]
fn unwrap_or_drop() {
// FIXME: Is doing this kind of loop reasonable? I tested `Arc::try_unwrap(x).ok()`
// and it makes this kind of assertion fail in roughly every second run somewhere
// between 1000 and 5000 iterations; I feel like doing a single iteration is too
// unlikely to catch anything interesting but doing too many is way too slow
// for a test that wouldn't ever fail for any reasonable implementation

for _ in 0..100
// ^ increase chances of hitting uncommon race conditions
{
use std::sync::Arc;
let x = Arc::new(3);
let y = Arc::clone(&x);
let r_thread = std::thread::spawn(|| Arc::try_unwrap(x).ok());
let s_thread = std::thread::spawn(|| Arc::try_unwrap(y).ok());
let r = r_thread.join().expect("r_thread panicked");
let s = s_thread.join().expect("s_thread panicked");
assert!(
matches!((r, s), (None, Some(3)) | (Some(3), None)),
"assertion failed: unexpected result `{:?}`\
\n expected `(None, Some(3))` or `(Some(3), None)`",
(r, s),
);
}

let x = Arc::new(3);
assert_eq!(Arc::unwrap_or_drop(x), Some(3));

let x = Arc::new(4);
let y = Arc::clone(&x);
assert_eq!(Arc::unwrap_or_drop(x), None);
assert_eq!(Arc::unwrap_or_drop(y), Some(4));

let x = Arc::new(5);
let _w = Arc::downgrade(&x);
assert_eq!(Arc::unwrap_or_drop(x), Some(5));
}

#[test]
fn into_from_raw() {
let x = Arc::new(box "hello");
Expand Down

0 comments on commit a534492

Please sign in to comment.