From eb98fa9e11138bf4c2f871cdf0af33a9a730ac1f Mon Sep 17 00:00:00 2001 From: Alice Ryhl <aliceryhl@google.com> Date: Sun, 19 Mar 2023 15:42:19 +0100 Subject: [PATCH 1/3] sync: try to lock the parent first in CancellationToken Deadlock safety requires that the parent is locked before the child, but we can still use `try_lock` to attempt to lock the parent without releasing the child. --- .../src/sync/cancellation_token/tree_node.rs | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/tokio-util/src/sync/cancellation_token/tree_node.rs b/tokio-util/src/sync/cancellation_token/tree_node.rs index 8f97dee8de5..ec55268cc2b 100644 --- a/tokio-util/src/sync/cancellation_token/tree_node.rs +++ b/tokio-util/src/sync/cancellation_token/tree_node.rs @@ -151,47 +151,43 @@ fn with_locked_node_and_parent<F, Ret>(node: &Arc<TreeNode>, func: F) -> Ret where F: FnOnce(MutexGuard<'_, Inner>, Option<MutexGuard<'_, Inner>>) -> Ret, { - let mut potential_parent = { - let locked_node = node.inner.lock().unwrap(); - match locked_node.parent.clone() { - Some(parent) => parent, - // If we locked the node and its parent is `None`, we are in a valid state - // and can return. - None => return func(locked_node, None), - } - }; + use std::sync::TryLockError; + let mut locked_node = node.inner.lock().unwrap(); + + // Every time this fails, the number of ancestors of the node decreases, + // so the loop must succeed after a finite number of iterations. loop { - // Deadlock safety: - // - // Due to invariant #2, we know that we have to lock the parent first, and then the child. - // This is true even if the potential_parent is no longer the current parent or even its - // sibling, as the invariant still holds. - let locked_parent = potential_parent.inner.lock().unwrap(); - let locked_node = node.inner.lock().unwrap(); - - let actual_parent = match locked_node.parent.clone() { - Some(parent) => parent, - // If we locked the node and its parent is `None`, we are in a valid state - // and can return. - None => { - // Was the wrong parent, so unlock it before calling `func` - drop(locked_parent); - return func(locked_node, None); - } + // Look up the parent of the currently locked node. + let potential_parent = match locked_node.parent.as_ref() { + Some(potential_parent) => potential_parent.clone(), + None => return func(locked_node, None), }; - // Loop until we managed to lock both the node and its parent - if Arc::ptr_eq(&actual_parent, &potential_parent) { - return func(locked_node, Some(locked_parent)); - } - - // Drop locked_parent before reassigning to potential_parent, - // as potential_parent is borrowed in it - drop(locked_node); - drop(locked_parent); + // Lock the parent. This may require unlocking the child first. + let locked_parent = match potential_parent.inner.try_lock() { + Ok(locked_parent) => locked_parent, + Err(TryLockError::WouldBlock) => { + drop(locked_node); + // Deadlock safety: + // + // Due to invariant #2, the potential parent must come before + // the child in the creation order. Therefore, we can safely + // lock the child while holding the parent lock. + let locked_parent = potential_parent.inner.lock().unwrap(); + locked_node = node.inner.lock().unwrap(); + locked_parent + }, + Err(err @ TryLockError::Poisoned(_)) => Err(err).unwrap(), + }; - potential_parent = actual_parent; + // If we unlocked the child, then the parent may have changed. Check + // that we still have the right parent. + if let Some(actual_parent) = locked_node.parent.as_ref() { + if Arc::ptr_eq(&actual_parent, &potential_parent) { + return func(locked_node, Some(locked_parent)); + } + } } } From 088a4826ed2ba4b2100b63e3c819422873be35c8 Mon Sep 17 00:00:00 2001 From: Alice Ryhl <aliceryhl@google.com> Date: Sun, 19 Mar 2023 15:47:12 +0100 Subject: [PATCH 2/3] rustfmt --- tokio-util/src/sync/cancellation_token/tree_node.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tokio-util/src/sync/cancellation_token/tree_node.rs b/tokio-util/src/sync/cancellation_token/tree_node.rs index ec55268cc2b..b75f4b90058 100644 --- a/tokio-util/src/sync/cancellation_token/tree_node.rs +++ b/tokio-util/src/sync/cancellation_token/tree_node.rs @@ -177,14 +177,14 @@ where let locked_parent = potential_parent.inner.lock().unwrap(); locked_node = node.inner.lock().unwrap(); locked_parent - }, + } Err(err @ TryLockError::Poisoned(_)) => Err(err).unwrap(), }; // If we unlocked the child, then the parent may have changed. Check // that we still have the right parent. if let Some(actual_parent) = locked_node.parent.as_ref() { - if Arc::ptr_eq(&actual_parent, &potential_parent) { + if Arc::ptr_eq(actual_parent, &potential_parent) { return func(locked_node, Some(locked_parent)); } } From 3735aaac85628525a423a510cf55a66c1c5269e6 Mon Sep 17 00:00:00 2001 From: Alice Ryhl <aliceryhl@google.com> Date: Sun, 19 Mar 2023 15:48:35 +0100 Subject: [PATCH 3/3] use PoisonError directly --- tokio-util/src/sync/cancellation_token/tree_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tokio-util/src/sync/cancellation_token/tree_node.rs b/tokio-util/src/sync/cancellation_token/tree_node.rs index b75f4b90058..f1abb4a8d33 100644 --- a/tokio-util/src/sync/cancellation_token/tree_node.rs +++ b/tokio-util/src/sync/cancellation_token/tree_node.rs @@ -178,7 +178,7 @@ where locked_node = node.inner.lock().unwrap(); locked_parent } - Err(err @ TryLockError::Poisoned(_)) => Err(err).unwrap(), + Err(TryLockError::Poisoned(err)) => Err(err).unwrap(), }; // If we unlocked the child, then the parent may have changed. Check