Skip to content

Commit 8af2a40

Browse files
committed
more comments and test, maybe won't want to keep all of them since it's a lot
squash me later
1 parent a534492 commit 8af2a40

File tree

2 files changed

+101
-1
lines changed

2 files changed

+101
-1
lines changed

library/alloc/src/sync.rs

Lines changed: 66 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,17 @@ impl<T> Arc<T> {
469469
///
470470
/// This will succeed even if there are outstanding weak references.
471471
///
472+
/// It is strongly recommended to use [`Arc::unwrap_or_drop`] instead if you don't
473+
/// want to keep the `Arc` in the [`Err`] case.
474+
/// Immediately dropping the [`Err`] payload, like in the expression
475+
/// `Arc::try_unwrap(this).ok()`, can still cause the strong count to
476+
/// drop to zero and the inner value of the `Arc` to be dropped:
477+
/// For instance if two threads execute this expression in parallel, then
478+
/// there is a race condition. The threads could first both check whether they
479+
/// have the last clone of their `Arc` via `try_unwrap`, and then
480+
/// both drop their `Arc` in the call to [`ok`][`Result::ok`],
481+
/// taking the strong count from two down to zero.
482+
///
472483
/// # Examples
473484
///
474485
/// ```
@@ -509,8 +520,11 @@ impl<T> Arc<T> {
509520
///
510521
/// If `unwrap_or_drop` is called on every clone of this `Arc`,
511522
/// it is guaranteed that exactly one of the calls returns the inner value.
523+
/// This means in particular that the inner value is not dropped.
524+
///
512525
/// The similar expression `Arc::try_unwrap(this).ok()` does not
513-
/// offer this guarantee.
526+
/// offer such a guarantee. See the last example below and the documentation
527+
/// of `try_unwrap`[`Arc::try_unwrap`].
514528
///
515529
/// # Examples
516530
///
@@ -522,16 +536,67 @@ impl<T> Arc<T> {
522536
/// let x = Arc::new(3);
523537
/// let y = Arc::clone(&x);
524538
///
539+
/// // Two threads calling `unwrap_or_drop` on both clones of an `Arc`:
525540
/// let x_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(x));
526541
/// let y_unwrap_thread = std::thread::spawn(|| Arc::unwrap_or_drop(y));
527542
///
528543
/// let x_unwrapped_value = x_unwrap_thread.join().unwrap();
529544
/// let y_unwrapped_value = y_unwrap_thread.join().unwrap();
530545
///
546+
/// // One of the threads is guaranteed to receive the inner value:
531547
/// assert!(matches!(
532548
/// (x_unwrapped_value, y_unwrapped_value),
533549
/// (None, Some(3)) | (Some(3), None)
534550
/// ));
551+
///
552+
///
553+
///
554+
/// // For a somewhat more practical example,
555+
/// // we define a singly linked list using `Arc`:
556+
/// #[derive(Clone)]
557+
/// struct LinkedList<T>(Option<Arc<Node<T>>>);
558+
/// struct Node<T>(T, Option<Arc<Node<T>>>);
559+
///
560+
/// // Dropping a long `LinkedList<T>` relying on the destructor of `Arc`
561+
/// // can cause a stack overflow. To prevent this, we can provide a
562+
/// // manual `Drop` implementation that does the destruction in a loop:
563+
/// impl<T> Drop for LinkedList<T> {
564+
/// fn drop(&mut self) {
565+
/// let mut x = self.0.take();
566+
/// while let Some(arc) = x.take() {
567+
/// Arc::unwrap_or_drop(arc).map(|node| x = node.1);
568+
/// }
569+
/// }
570+
/// }
571+
///
572+
/// // implementation of `new` and `push` omitted
573+
/// impl<T> LinkedList<T> {
574+
/// /* ... */
575+
/// # fn new() -> Self {
576+
/// # LinkedList(None)
577+
/// # }
578+
/// # fn push(&mut self, x: T) {
579+
/// # self.0 = Some(Arc::new(Node(x, self.0.take())));
580+
/// # }
581+
/// }
582+
///
583+
/// // The following code could still cause a stack overflow
584+
/// // despite the manual `Drop` impl if that `Drop` impl used
585+
/// // `Arc::try_unwrap(arc).ok()` instead of `Arc::unwrap_or_drop(arc)`.
586+
/// {
587+
/// // create a long list and clone it
588+
/// let mut x = LinkedList::new();
589+
/// for i in 0..100000 {
590+
/// x.push(i); // adds i to the front of x
591+
/// }
592+
/// let y = x.clone();
593+
///
594+
/// // drop the clones in parallel
595+
/// let t1 = std::thread::spawn(|| drop(x));
596+
/// let t2 = std::thread::spawn(|| drop(y));
597+
/// t1.join().unwrap();
598+
/// t2.join().unwrap();
599+
/// }
535600
/// ```
536601
#[inline]
537602
#[unstable(feature = "unwrap_or_drop", issue = "none")] // FIXME: add issue

library/alloc/src/sync/tests.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,41 @@ fn unwrap_or_drop() {
140140
assert_eq!(Arc::unwrap_or_drop(x), Some(5));
141141
}
142142

143+
#[test]
144+
fn unwrap_or_drop_linked_list() {
145+
#[derive(Clone)]
146+
struct LinkedList<T>(Option<Arc<Node<T>>>);
147+
struct Node<T>(T, Option<Arc<Node<T>>>);
148+
149+
impl<T> Drop for LinkedList<T> {
150+
fn drop(&mut self) {
151+
let mut x = self.0.take();
152+
while let Some(arc) = x.take() {
153+
Arc::unwrap_or_drop(arc).map(|node| x = node.1);
154+
}
155+
}
156+
}
157+
158+
impl<T> LinkedList<T> {
159+
fn push(&mut self, x: T) {
160+
self.0 = Some(Arc::new(Node(x, self.0.take())));
161+
}
162+
}
163+
164+
use std::thread;
165+
for _ in 0..25 {
166+
let mut x = LinkedList(None);
167+
for i in 0..100000 {
168+
x.push(i);
169+
}
170+
let y = x.clone();
171+
let t1 = thread::spawn(|| drop(x));
172+
let t2 = thread::spawn(|| drop(y));
173+
t1.join().unwrap();
174+
t2.join().unwrap();
175+
}
176+
}
177+
143178
#[test]
144179
fn into_from_raw() {
145180
let x = Arc::new(box "hello");

0 commit comments

Comments
 (0)