From 77446d3b504c653edf280c73c2c1c9b99a11aa28 Mon Sep 17 00:00:00 2001 From: Max Inden <mail@max-inden.de> Date: Wed, 25 Mar 2020 19:45:04 +0100 Subject: [PATCH] client/finality-grandpa/src/until_imported: Refactor BlockGlobalMessage (#5390) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * client/finality-grandpa/src/until_imported: Refactor BlockGlobalMessage `BlockGlobalMessage` owns an `inner` which contains (1) a count for the amount of outstanding blocks to be waited on and (2) the message itself. Given that both is already wrapped in an `Arc` there is no need to keep track of the outstanding blocks, given that it simply corresponds to the amount of strong reference counts on the `Arc` itself. This commit removes the atomic counter within `inner` and piggy backs on the `Arc` reference counter instead. * client/finality-grandpa/src/until_imported: Remove useless match * client/finality-grandpa/src/until_imported.rs: Remove unused var Co-Authored-By: André Silva <andre.beat@gmail.com> * client/finality-grandpa/src/until_imported: Address comment suggestion Co-authored-by: André Silva <andre.beat@gmail.com> --- .../finality-grandpa/src/until_imported.rs | 105 ++++++++++++++---- 1 file changed, 81 insertions(+), 24 deletions(-) diff --git a/substrate/client/finality-grandpa/src/until_imported.rs b/substrate/client/finality-grandpa/src/until_imported.rs index c3a52fcf56f..c3804e1272f 100644 --- a/substrate/client/finality-grandpa/src/until_imported.rs +++ b/substrate/client/finality-grandpa/src/until_imported.rs @@ -40,7 +40,7 @@ use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::collections::{HashMap, VecDeque}; use std::pin::Pin; -use std::sync::{atomic::{AtomicUsize, Ordering}, Arc}; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use sp_finality_grandpa::AuthorityId; @@ -307,8 +307,12 @@ pub(crate) type UntilVoteTargetImported<Block, BlockStatus, BlockSyncRequester, /// /// This is used for compact commits and catch up messages which have already /// been checked for structural soundness (e.g. valid signatures). +/// +/// We use the `Arc`'s reference count to implicitly count the number of outstanding blocks that we +/// are waiting on for the same message (i.e. other `BlockGlobalMessage` instances with the same +/// `inner`). pub(crate) struct BlockGlobalMessage<Block: BlockT> { - inner: Arc<(AtomicUsize, Mutex<Option<CommunicationIn<Block>>>)>, + inner: Arc<Mutex<Option<CommunicationIn<Block>>>>, target_number: NumberFor<Block>, } @@ -416,7 +420,7 @@ impl<Block: BlockT> BlockUntilImported<Block> for BlockGlobalMessage<Block> { return Ok(()) } - let locked_global = Arc::new((AtomicUsize::new(unknown_count), Mutex::new(Some(input)))); + let locked_global = Arc::new(Mutex::new(Some(input))); // schedule waits for all unknown messages. // when the last one of these has `wait_completed` called on it, @@ -438,30 +442,20 @@ impl<Block: BlockT> BlockUntilImported<Block> for BlockGlobalMessage<Block> { fn wait_completed(self, canon_number: NumberFor<Block>) -> Option<Self::Blocked> { if self.target_number != canon_number { - // if we return without deducting the counter, then none of the other - // handles can return the commit message. + // Delete the inner message so it won't ever be forwarded. Future calls to + // `wait_completed` on the same `inner` will ignore it. + *self.inner.lock() = None; return None; } - let mut last_count = self.inner.0.load(Ordering::Acquire); - - // CAS loop to ensure that we always have a last reader. - loop { - if last_count == 1 { // we are the last one left. - return self.inner.1.lock().take(); - } - - let prev_value = self.inner.0.compare_and_swap( - last_count, - last_count - 1, - Ordering::SeqCst, - ); - - if prev_value == last_count { - return None; - } else { - last_count = prev_value; - } + match Arc::try_unwrap(self.inner) { + // This is the last reference and thus the last outstanding block to be awaited. `inner` + // is either `Some(_)` or `None`. The latter implies that a previous `wait_completed` + // call witnessed a block number mismatch (see above). + Ok(inner) => Mutex::into_inner(inner), + // There are still other strong references to this `Arc`, thus the message is blocked on + // other blocks to be imported. + Err(_) => None, } } } @@ -941,4 +935,67 @@ mod tests { futures::executor::block_on(test); } + + fn test_catch_up() -> Arc<Mutex<Option<CommunicationIn<Block>>>> { + let header = make_header(5); + + let unknown_catch_up = finality_grandpa::CatchUp { + round_number: 1, + precommits: vec![], + prevotes: vec![], + base_hash: header.hash(), + base_number: *header.number(), + }; + + let catch_up = voter::CommunicationIn::CatchUp( + unknown_catch_up.clone(), + voter::Callback::Blank, + ); + + Arc::new(Mutex::new(Some(catch_up))) + } + + #[test] + fn block_global_message_wait_completed_return_when_all_awaited() { + let msg_inner = test_catch_up(); + + let waiting_block_1 = BlockGlobalMessage::<Block> { + inner: msg_inner.clone(), + target_number: 1, + }; + + let waiting_block_2 = BlockGlobalMessage::<Block> { + inner: msg_inner, + target_number: 2, + }; + + // waiting_block_2 is still waiting for block 2, thus this should return `None`. + assert!(waiting_block_1.wait_completed(1).is_none()); + + // Message only depended on block 1 and 2. Both have been imported, thus this should yield + // the message. + assert!(waiting_block_2.wait_completed(2).is_some()); + } + + #[test] + fn block_global_message_wait_completed_return_none_on_block_number_missmatch() { + let msg_inner = test_catch_up(); + + let waiting_block_1 = BlockGlobalMessage::<Block> { + inner: msg_inner.clone(), + target_number: 1, + }; + + let waiting_block_2 = BlockGlobalMessage::<Block> { + inner: msg_inner, + target_number: 2, + }; + + // Calling wait_completed with wrong block number should yield None. + assert!(waiting_block_1.wait_completed(1234).is_none()); + + // All blocks, that the message depended on, have been imported. Still, given the above + // block number mismatch this should return None. + assert!(waiting_block_2.wait_completed(2).is_none()); + } } -- GitLab