Adds start and stop work heartbeats. (#1188)

* Adds start and stop work heartbeats.

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Fix code after suggested changes

* Finalizing stops work on earlier lower blocks.

* Fix func parameter and flaky test

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
Fedor Sakharov
2020-06-03 09:30:21 +03:00
committed by GitHub
parent 593fa2b0c1
commit fc45a8e673
4 changed files with 333 additions and 13 deletions
+1
View File
@@ -3308,6 +3308,7 @@ dependencies = [
"futures-timer 3.0.2", "futures-timer 3.0.2",
"kv-log-macro", "kv-log-macro",
"log 0.4.8", "log 0.4.8",
"polkadot-primitives",
"streamunordered", "streamunordered",
] ]
+1
View File
@@ -9,6 +9,7 @@ futures = "0.3.5"
log = "0.4.8" log = "0.4.8"
futures-timer = "3.0.2" futures-timer = "3.0.2"
streamunordered = "0.5.1" streamunordered = "0.5.1"
polkadot-primitives = { path = "../primitives" }
[dev-dependencies] [dev-dependencies]
futures = { version = "0.3.5", features = ["thread-pool"] } futures = { version = "0.3.5", features = ["thread-pool"] }
@@ -111,6 +111,7 @@ fn main() {
}); });
let (overseer, _handler) = Overseer::new( let (overseer, _handler) = Overseer::new(
vec![],
Box::new(Subsystem2), Box::new(Subsystem2),
Box::new(Subsystem1), Box::new(Subsystem1),
spawner, spawner,
+330 -13
View File
@@ -58,6 +58,7 @@ use std::fmt::Debug;
use std::pin::Pin; use std::pin::Pin;
use std::task::Poll; use std::task::Poll;
use std::time::Duration; use std::time::Duration;
use std::collections::HashSet;
use futures::channel::{mpsc, oneshot}; use futures::channel::{mpsc, oneshot};
use futures::{ use futures::{
@@ -70,6 +71,8 @@ use futures::{
use futures_timer::Delay; use futures_timer::Delay;
use streamunordered::{StreamYield, StreamUnordered}; use streamunordered::{StreamYield, StreamUnordered};
use polkadot_primitives::{BlockNumber, Hash};
/// An error type that describes faults that may happen /// An error type that describes faults that may happen
/// ///
/// These are: /// These are:
@@ -136,10 +139,25 @@ enum ToOverseer {
}, },
} }
/// An event telling the `Overseer` on the particular block
/// that has been imported or finalized.
///
/// This structure exists solely for the purposes of decoupling
/// `Overseer` code from the client code and the necessity to call
/// `HeaderBackend::block_number_from_id()`.
pub struct BlockInfo {
/// hash of the block.
pub hash: Hash,
/// hash of the parent block.
pub parent_hash: Hash,
/// block's number.
pub number: BlockNumber,
}
/// Some event from outer world. /// Some event from outer world.
enum Event { enum Event {
BlockImport, BlockImported(BlockInfo),
BlockFinalized, BlockFinalized(BlockInfo),
MsgToSubsystem(AllMessages), MsgToSubsystem(AllMessages),
Stop, Stop,
} }
@@ -160,8 +178,8 @@ pub struct OverseerHandler {
impl OverseerHandler { impl OverseerHandler {
/// Inform the `Overseer` that that some block was imported. /// Inform the `Overseer` that that some block was imported.
pub async fn block_imported(&mut self) -> SubsystemResult<()> { pub async fn block_imported(&mut self, block: BlockInfo) -> SubsystemResult<()> {
self.events_tx.send(Event::BlockImport).await?; self.events_tx.send(Event::BlockImported(block)).await?;
Ok(()) Ok(())
} }
@@ -174,8 +192,8 @@ impl OverseerHandler {
} }
/// Inform the `Overseer` that that some block was finalized. /// Inform the `Overseer` that that some block was finalized.
pub async fn block_finalized(&mut self) -> SubsystemResult<()> { pub async fn block_finalized(&mut self, block: BlockInfo) -> SubsystemResult<()> {
self.events_tx.send(Event::BlockFinalized).await?; self.events_tx.send(Event::BlockFinalized(block)).await?;
Ok(()) Ok(())
} }
@@ -222,12 +240,12 @@ pub struct SubsystemContext<M: Debug>{
/// ///
/// [`Overseer`]: struct.Overseer.html /// [`Overseer`]: struct.Overseer.html
/// [`Subsystem`]: trait.Subsystem.html /// [`Subsystem`]: trait.Subsystem.html
#[derive(Debug)] #[derive(PartialEq, Clone, Debug)]
pub enum OverseerSignal { pub enum OverseerSignal {
/// `Subsystem` should start working. /// `Subsystem` should start working.
StartWork, StartWork(Hash),
/// `Subsystem` should stop working. /// `Subsystem` should stop working.
StopWork, StopWork(Hash),
/// Conclude the work of the `Overseer` and all `Subsystem`s. /// Conclude the work of the `Overseer` and all `Subsystem`s.
Conclude, Conclude,
} }
@@ -366,6 +384,14 @@ pub struct Overseer<S: Spawn> {
/// Events that are sent to the overseer from the outside world /// Events that are sent to the overseer from the outside world
events_rx: mpsc::Receiver<Event>, events_rx: mpsc::Receiver<Event>,
/// A set of leaves that `Overseer` starts working with.
///
/// Drained at the beginning of `run` and never used again.
leaves: Vec<(Hash, BlockNumber)>,
/// The set of the "active leaves".
active_leaves: HashSet<(Hash, BlockNumber)>,
} }
impl<S> Overseer<S> impl<S> Overseer<S>
@@ -452,6 +478,7 @@ where
/// # fn main() { executor::block_on(async move { /// # fn main() { executor::block_on(async move {
/// let spawner = executor::ThreadPool::new().unwrap(); /// let spawner = executor::ThreadPool::new().unwrap();
/// let (overseer, _handler) = Overseer::new( /// let (overseer, _handler) = Overseer::new(
/// vec![],
/// Box::new(ValidationSubsystem), /// Box::new(ValidationSubsystem),
/// Box::new(CandidateBackingSubsystem), /// Box::new(CandidateBackingSubsystem),
/// spawner, /// spawner,
@@ -471,6 +498,7 @@ where
/// # }); } /// # }); }
/// ``` /// ```
pub fn new( pub fn new(
leaves: impl IntoIterator<Item = BlockInfo>,
validation: Box<dyn Subsystem<ValidationSubsystemMessage> + Send>, validation: Box<dyn Subsystem<ValidationSubsystemMessage> + Send>,
candidate_backing: Box<dyn Subsystem<CandidateBackingSubsystemMessage> + Send>, candidate_backing: Box<dyn Subsystem<CandidateBackingSubsystemMessage> + Send>,
mut s: S, mut s: S,
@@ -498,6 +526,13 @@ where
candidate_backing, candidate_backing,
)?; )?;
let active_leaves = HashSet::new();
let leaves = leaves
.into_iter()
.map(|BlockInfo { hash, parent_hash: _, number }| (hash, number))
.collect();
let this = Self { let this = Self {
validation_subsystem, validation_subsystem,
candidate_backing_subsystem, candidate_backing_subsystem,
@@ -505,6 +540,8 @@ where
running_subsystems, running_subsystems,
running_subsystems_rx, running_subsystems_rx,
events_rx, events_rx,
leaves,
active_leaves,
}; };
Ok((this, handler)) Ok((this, handler))
@@ -537,6 +574,13 @@ where
/// Run the `Overseer`. /// Run the `Overseer`.
pub async fn run(mut self) -> SubsystemResult<()> { pub async fn run(mut self) -> SubsystemResult<()> {
let leaves = std::mem::take(&mut self.leaves);
for leaf in leaves.into_iter() {
self.broadcast_signal(OverseerSignal::StartWork(leaf.0)).await?;
self.active_leaves.insert(leaf);
}
loop { loop {
while let Poll::Ready(Some(msg)) = poll!(&mut self.events_rx.next()) { while let Poll::Ready(Some(msg)) = poll!(&mut self.events_rx.next()) {
match msg { match msg {
@@ -547,7 +591,12 @@ where
self.stop().await; self.stop().await;
return Ok(()); return Ok(());
} }
_ => () Event::BlockImported(block) => {
self.block_imported(block).await?;
}
Event::BlockFinalized(block) => {
self.block_finalized(block).await?;
}
} }
} }
@@ -576,6 +625,50 @@ where
} }
} }
async fn block_imported(&mut self, block: BlockInfo) -> SubsystemResult<()> {
if let Some(parent) = self.active_leaves.take(&(block.parent_hash, block.number - 1)) {
self.broadcast_signal(OverseerSignal::StopWork(parent.0)).await?;
}
if !self.active_leaves.contains(&(block.hash, block.number)) {
self.broadcast_signal(OverseerSignal::StartWork(block.hash)).await?;
self.active_leaves.insert((block.hash, block.number));
}
Ok(())
}
async fn block_finalized(&mut self, block: BlockInfo) -> SubsystemResult<()> {
let mut stop_these = Vec::new();
self.active_leaves.retain(|(h, n)| {
if *n <= block.number {
stop_these.push(*h);
false
} else {
true
}
});
for hash in stop_these.into_iter() {
self.broadcast_signal(OverseerSignal::StopWork(hash)).await?
}
Ok(())
}
async fn broadcast_signal(&mut self, signal: OverseerSignal) -> SubsystemResult<()> {
if let Some(ref mut s) = self.validation_subsystem.instance {
s.tx.send(FromOverseer::Signal(signal.clone())).await?;
}
if let Some(ref mut s) = self.candidate_backing_subsystem.instance {
s.tx.send(FromOverseer::Signal(signal)).await?;
}
Ok(())
}
async fn route_message(&mut self, msg: AllMessages) { async fn route_message(&mut self, msg: AllMessages) {
match msg { match msg {
AllMessages::Validation(msg) => { AllMessages::Validation(msg) => {
@@ -591,7 +684,6 @@ where
} }
} }
fn spawn_job(&mut self, j: BoxFuture<'static, ()>) -> SubsystemResult<()> { fn spawn_job(&mut self, j: BoxFuture<'static, ()>) -> SubsystemResult<()> {
self.s.spawn(j).map_err(|_| SubsystemError) self.s.spawn(j).map_err(|_| SubsystemError)
} }
@@ -642,7 +734,7 @@ mod tests {
i += 1; i += 1;
continue; continue;
} }
Ok(FromOverseer::Signal(OverseerSignal::StopWork)) => return, Ok(FromOverseer::Signal(OverseerSignal::Conclude)) => return,
Err(_) => return, Err(_) => return,
_ => (), _ => (),
} }
@@ -668,7 +760,7 @@ mod tests {
continue; continue;
} }
match ctx.try_recv().await { match ctx.try_recv().await {
Ok(Some(FromOverseer::Signal(OverseerSignal::StopWork))) => { Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => {
break; break;
} }
Ok(Some(_)) => { Ok(Some(_)) => {
@@ -703,6 +795,7 @@ mod tests {
let (s2_tx, mut s2_rx) = mpsc::channel(64); let (s2_tx, mut s2_rx) = mpsc::channel(64);
let (overseer, mut handler) = Overseer::new( let (overseer, mut handler) = Overseer::new(
vec![],
Box::new(TestSubsystem1(s1_tx)), Box::new(TestSubsystem1(s1_tx)),
Box::new(TestSubsystem2(s2_tx)), Box::new(TestSubsystem2(s2_tx)),
spawner, spawner,
@@ -752,6 +845,7 @@ mod tests {
executor::block_on(async move { executor::block_on(async move {
let (s1_tx, _) = mpsc::channel(64); let (s1_tx, _) = mpsc::channel(64);
let (overseer, _handle) = Overseer::new( let (overseer, _handle) = Overseer::new(
vec![],
Box::new(TestSubsystem1(s1_tx)), Box::new(TestSubsystem1(s1_tx)),
Box::new(TestSubsystem4), Box::new(TestSubsystem4),
spawner, spawner,
@@ -765,4 +859,227 @@ mod tests {
} }
}) })
} }
struct TestSubsystem5(mpsc::Sender<OverseerSignal>);
impl Subsystem<ValidationSubsystemMessage> for TestSubsystem5 {
fn start(&mut self, mut ctx: SubsystemContext<ValidationSubsystemMessage>) -> SpawnedSubsystem {
let mut sender = self.0.clone();
SpawnedSubsystem(Box::pin(async move {
loop {
match ctx.try_recv().await {
Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break,
Ok(Some(FromOverseer::Signal(s))) => {
sender.send(s).await.unwrap();
continue;
},
Ok(Some(_)) => continue,
Err(_) => return,
_ => (),
}
pending!();
}
}))
}
}
struct TestSubsystem6(mpsc::Sender<OverseerSignal>);
impl Subsystem<CandidateBackingSubsystemMessage> for TestSubsystem6 {
fn start(&mut self, mut ctx: SubsystemContext<CandidateBackingSubsystemMessage>) -> SpawnedSubsystem {
let mut sender = self.0.clone();
SpawnedSubsystem(Box::pin(async move {
loop {
match ctx.try_recv().await {
Ok(Some(FromOverseer::Signal(OverseerSignal::Conclude))) => break,
Ok(Some(FromOverseer::Signal(s))) => {
sender.send(s).await.unwrap();
continue;
},
Ok(Some(_)) => continue,
Err(_) => return,
_ => (),
}
pending!();
}
}))
}
}
// Tests that starting with a defined set of leaves and receiving
// notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats.
#[test]
fn overseer_start_stop_works() {
let spawner = executor::ThreadPool::new().unwrap();
executor::block_on(async move {
let first_block_hash = [1; 32].into();
let second_block_hash = [2; 32].into();
let third_block_hash = [3; 32].into();
let first_block = BlockInfo {
hash: first_block_hash,
parent_hash: [0; 32].into(),
number: 1,
};
let second_block = BlockInfo {
hash: second_block_hash,
parent_hash: first_block_hash,
number: 2,
};
let third_block = BlockInfo {
hash: third_block_hash,
parent_hash: second_block_hash,
number: 3,
};
let (tx_5, mut rx_5) = mpsc::channel(64);
let (tx_6, mut rx_6) = mpsc::channel(64);
let (overseer, mut handler) = Overseer::new(
vec![first_block],
Box::new(TestSubsystem5(tx_5)),
Box::new(TestSubsystem6(tx_6)),
spawner,
).unwrap();
let overseer_fut = overseer.run().fuse();
pin_mut!(overseer_fut);
let mut ss5_results = Vec::new();
let mut ss6_results = Vec::new();
handler.block_imported(second_block).await.unwrap();
handler.block_imported(third_block).await.unwrap();
let expected_heartbeats = vec![
OverseerSignal::StartWork(first_block_hash),
OverseerSignal::StopWork(first_block_hash),
OverseerSignal::StartWork(second_block_hash),
OverseerSignal::StopWork(second_block_hash),
OverseerSignal::StartWork(third_block_hash),
];
loop {
select! {
res = overseer_fut => {
assert!(res.is_ok());
break;
},
res = rx_5.next() => {
if let Some(res) = res {
ss5_results.push(res);
}
}
res = rx_6.next() => {
if let Some(res) = res {
ss6_results.push(res);
}
}
complete => break,
}
if ss5_results.len() == expected_heartbeats.len() &&
ss6_results.len() == expected_heartbeats.len() {
handler.stop().await.unwrap();
}
}
assert_eq!(ss5_results, expected_heartbeats);
assert_eq!(ss6_results, expected_heartbeats);
});
}
// Tests that starting with a defined set of leaves and receiving
// notifications on imported blocks triggers expected `StartWork` and `StopWork` heartbeats.
#[test]
fn overseer_finalize_works() {
let spawner = executor::ThreadPool::new().unwrap();
executor::block_on(async move {
let first_block_hash = [1; 32].into();
let second_block_hash = [2; 32].into();
let third_block_hash = [3; 32].into();
let first_block = BlockInfo {
hash: first_block_hash,
parent_hash: [0; 32].into(),
number: 1,
};
let second_block = BlockInfo {
hash: second_block_hash,
parent_hash: [42; 32].into(),
number: 2,
};
let third_block = BlockInfo {
hash: third_block_hash,
parent_hash: second_block_hash,
number: 3,
};
let (tx_5, mut rx_5) = mpsc::channel(64);
let (tx_6, mut rx_6) = mpsc::channel(64);
// start with two forks of different height.
let (overseer, mut handler) = Overseer::new(
vec![first_block, second_block],
Box::new(TestSubsystem5(tx_5)),
Box::new(TestSubsystem6(tx_6)),
spawner,
).unwrap();
let overseer_fut = overseer.run().fuse();
pin_mut!(overseer_fut);
let mut ss5_results = Vec::new();
let mut ss6_results = Vec::new();
// this should stop work on both forks we started with earlier.
handler.block_finalized(third_block).await.unwrap();
let expected_heartbeats = vec![
OverseerSignal::StartWork(first_block_hash),
OverseerSignal::StartWork(second_block_hash),
OverseerSignal::StopWork(first_block_hash),
OverseerSignal::StopWork(second_block_hash),
];
loop {
select! {
res = overseer_fut => {
assert!(res.is_ok());
break;
},
res = rx_5.next() => {
if let Some(res) = res {
ss5_results.push(res);
}
}
res = rx_6.next() => {
if let Some(res) = res {
ss6_results.push(res);
}
}
complete => break,
}
if ss5_results.len() == expected_heartbeats.len() &&
ss6_results.len() == expected_heartbeats.len() {
handler.stop().await.unwrap();
}
}
assert_eq!(ss5_results.len(), expected_heartbeats.len());
assert_eq!(ss6_results.len(), expected_heartbeats.len());
// Notifications on finality for multiple blocks at once
// may be received in different orders.
for expected in expected_heartbeats {
assert!(ss5_results.contains(&expected));
assert!(ss6_results.contains(&expected));
}
});
}
} }