-
Notifications
You must be signed in to change notification settings - Fork 2.6k
client/finality-grandpa: Reintegrate periodic neighbor packet worker #4631
Conversation
The `NeighborPacketWorker` within `client/finality-grandpa` does two things: 1. It receives neighbor packets from components within `client/finality-grandpa`, sends them down to the `GossipEngine` in order for neighboring nodes to receive. 2. It periodically sends out the most recent neighbor packet to the `GossipEngine`. In order to send out packets it had a clone to a `GossipEgine` within an atomic reference counter and a mutex. The `NeighborPacketWorker` was then spawned onto its own asynchronous task. Instead of running in its own task, this patch reintegrates the `NeighborPacketWorker` into the main `client/finality-grandpa` task not requiring the `NeighborPacketWorker` to own a clone of the `GossipEngine`. The greater picture This is a tiny change within a greater refactoring. The overall goal is to **simplify** how finality-grandpa interacts with the network and to **reduce** the amount of **unbounded channels** within the logic. Why no unbounded channels: Bounding channels is needed for backpressure and proper scheduling. With unbounded channels there is no way of telling the producer side to slow down for the consumer side to catch up. Rephrased, there is no way for the scheduler to know when to favour the consumer task over the producer task on a crowded channel and the other way round for an empty channel. Reducing the amount of shared ownership simplifies the logic and enables one to use async-await syntax-suggar, given that one does not need to hold a lock across poll invocations. Using async-await enables one to use bounded channels without complex logic.
// | ||
// NetworkBridge is required to be clonable, thus one needs to be able to clone its children, | ||
// thus one has to wrap neighor_packet_worker with an Arc Mutex. | ||
neighbor_packet_worker: Arc<Mutex<periodic::NeighborPacketWorker<B>>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in order to reduce the amount of Mutex
es, I introduce a new one here. This one is within the crate only and can be removed once NetworkBridge
does not need to be clonable anymore.
/// Sender side of the neighbor packet channel. | ||
/// | ||
/// Packets send into this channel are processed by the `NeighborPacketWorker` and passed on to | ||
/// the underlying `GossipEngine`. | ||
neighbor_sender: periodic::NeighborPacketSender<B>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
neighbor_sender: periodic::NeighborPacketSender<B>, | |
neighbor_packet_sender: periodic::NeighborPacketSender<B>, |
Happy to do this change, but rather as a follow up to reduce the noise within this pull request.
client/finality-grandpa/src/lib.rs
Outdated
SC: SelectChain<Block> + 'static, | ||
NumberFor<Block>: BlockNumberOps, | ||
NumberFor<Block>: BlockNumberOps + Unpin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong
NeighborPacketSender<B>, | ||
) where | ||
B: BlockT, | ||
pub(super) struct NeighborPacketWorker<B: BlockT> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could use a doc-string
@@ -391,6 +411,24 @@ impl<B: BlockT, N: Network<B>> NetworkBridge<B, N> { | |||
} | |||
} | |||
|
|||
impl<B: BlockT, N: Network<B>> Future03 for NetworkBridge<B, N> | |||
where | |||
NumberFor<B>: Unpin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NumberFor<B>: Unpin, |
You probably have to add impl<B, N> Unpin for NetworkBridge<B, N> {}
somewhere instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, thank you and thank you @tomaka ❤️
|
||
impl <B: BlockT> Stream for NeighborPacketWorker<B> | ||
where | ||
NumberFor<B>: Unpin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NumberFor<B>: Unpin, |
client/finality-grandpa/src/lib.rs
Outdated
SC: SelectChain<Block> + 'static, | ||
VR: VotingRule<Block, Client<B, E, Block, RA>> + Clone + 'static, | ||
NumberFor<Block>: BlockNumberOps, | ||
NumberFor<Block>: BlockNumberOps + Unpin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NumberFor<Block>: BlockNumberOps + Unpin, | |
NumberFor<Block>: BlockNumberOps, |
client/finality-grandpa/src/lib.rs
Outdated
@@ -551,10 +551,10 @@ pub fn run_grandpa_voter<B, E, Block: BlockT, N, RA, SC, VR, X, Sp>( | |||
Block::Hash: Ord, | |||
B: Backend<Block> + 'static, | |||
E: CallExecutor<Block> + Send + Sync + 'static, | |||
N: NetworkT<Block> + Send + Sync + Clone + 'static, | |||
N: NetworkT<Block> + Send + Sync + Clone + Unpin + 'static, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure about this Unpin
client/finality-grandpa/src/lib.rs
Outdated
N: NetworkT<Block> + Sync, | ||
NumberFor<Block>: BlockNumberOps, | ||
N: NetworkT<Block> + Sync + Unpin, | ||
NumberFor<Block>: BlockNumberOps + Unpin, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, you get the idea
Instead of requiring `Unpin` on all generic trait bounds involved in anything ever `poll`ed, one can implement `Unpin` on the structs being `poll`ed themselves.
@tomaka can you take another look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not 100% confident in the logic of the change because I'm not familiar with the code base, but nothing looks wrong to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Co-Authored-By: André Silva <andre.beat@gmail.com>
Co-Authored-By: André Silva <andre.beat@gmail.com>
The
NeighborPacketWorker
withinclient/finality-grandpa
does twothings:
It receives neighbor packets from components within
client/finality-grandpa
, sends them down to theGossipEngine
inorder for neighboring nodes to receive.
It periodically sends out the most recent neighbor packet to the
GossipEngine
.In order to send out packets it had a clone to a
GossipEgine
withinan atomic reference counter and a mutex. The
NeighborPacketWorker
wasthen spawned onto its own asynchronous task.
Instead of running in its own task, this patch reintegrates the
NeighborPacketWorker
into the mainclient/finality-grandpa
task notrequiring the
NeighborPacketWorker
to own a clone of theGossipEngine
.The greater picture
This is a tiny change within a greater refactoring. The overall goal is
to simplify how finality-grandpa interacts with the network and to
reduce the amount of unbounded channels within the logic.
Why no unbounded channels: Bounding channels is needed for backpressure
and proper scheduling. With unbounded channels there is no way of
telling the producer side to slow down for the consumer side to catch
up. Rephrased, there is no way for the scheduler to know when to favour
the consumer task over the producer task on a crowded channel and the
other way round for an empty channel.
Reducing the amount of shared ownership simplifies the logic and enables
one to use async-await syntax-suggar, given that one does not need to
hold a lock across poll invocations. Using async-await enables one to
use bounded channels without complex logic.