refactor overseer into proc-macro based pattern (#2962)

This commit is contained in:
Bernhard Schuster
2021-07-08 21:09:26 +02:00
committed by GitHub
parent 2510bfc5d7
commit 3c9104daff
119 changed files with 5675 additions and 3864 deletions
@@ -0,0 +1,22 @@
[package]
name = "polkadot-overseer-gen"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
description = "Generate an overseer including builder pattern and message wrapper from a single struct."
[dependencies]
tracing = "0.1"
futures = "0.3"
async-trait = "0.1"
thiserror = "1"
metered = { package = "metered-channel", path = "../../metered-channel" }
polkadot-overseer-gen-proc-macro = { path = "./proc-macro" }
polkadot-node-network-protocol = { path = "../../network/protocol"}
# trait SpawnNamed
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
futures-timer = "3.0.2"
pin-project = "1.0"
[dev-dependencies]
trybuild = "1.0.41"
@@ -0,0 +1,134 @@
//! A dummy to be used with cargo expand
use polkadot_overseer_gen::*;
use polkadot_node_network_protocol::WrongVariant;
/// Concrete subsystem implementation for `MsgStrukt` msg type.
#[derive(Default)]
pub struct AwesomeSubSys;
impl ::polkadot_overseer_gen::Subsystem<XxxSubsystemContext<MsgStrukt>, Yikes> for AwesomeSubSys {
fn start(self, _ctx: XxxSubsystemContext<MsgStrukt>) -> SpawnedSubsystem < Yikes > {
unimplemented!("starting yay!")
}
}
#[derive(Default)]
pub struct GoblinTower;
impl ::polkadot_overseer_gen::Subsystem<XxxSubsystemContext<Plinko>, Yikes> for GoblinTower {
fn start(self, _ctx: XxxSubsystemContext<Plinko>) -> SpawnedSubsystem < Yikes > {
unimplemented!("welcum")
}
}
/// A signal sent by the overseer.
#[derive(Debug, Clone)]
pub struct SigSigSig;
/// The external event.
#[derive(Debug, Clone)]
pub struct EvX;
impl EvX {
pub fn focus<'a, T>(&'a self) -> Result<EvX, ()> {
unimplemented!("dispatch")
}
}
#[derive(Debug, Clone, Copy)]
pub struct Yikes;
impl std::fmt::Display for Yikes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "yikes!")
}
}
impl std::error::Error for Yikes {}
impl From<polkadot_overseer_gen::OverseerError> for Yikes {
fn from(_: polkadot_overseer_gen::OverseerError) -> Yikes {
Yikes
}
}
impl From<polkadot_overseer_gen::mpsc::SendError> for Yikes {
fn from(_: polkadot_overseer_gen::mpsc::SendError) -> Yikes {
Yikes
}
}
#[derive(Debug, Clone)]
pub struct MsgStrukt(u8);
#[derive(Debug, Clone, Copy)]
pub struct Plinko;
impl From<NetworkMsg> for MsgStrukt {
fn from(_event: NetworkMsg) -> Self {
MsgStrukt(1u8)
}
}
#[derive(Debug, Clone, Copy)]
pub enum NetworkMsg {
A,
B,
C,
}
impl NetworkMsg {
fn focus(&self) -> Result<Self, WrongVariant> {
Ok(match self {
Self::B => return Err(WrongVariant),
Self::A | Self::C => self.clone()
})
}
}
#[overlord(signal=SigSigSig, event=EvX, error=Yikes, network=NetworkMsg, gen=AllMessages)]
struct Xxx {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
#[subsystem(no_dispatch, blocking, Plinko)]
plinkos: GoblinTower,
i_like_pi: f64,
}
#[derive(Debug, Clone)]
struct DummySpawner;
impl SpawnNamed for DummySpawner{
fn spawn_blocking(&self, name: &'static str, _future: futures::future::BoxFuture<'static, ()>) {
unimplemented!("spawn blocking {}", name)
}
fn spawn(&self, name: &'static str, _future: futures::future::BoxFuture<'static, ()>) {
unimplemented!("spawn {}", name)
}
}
#[derive(Debug, Clone)]
struct DummyCtx;
fn main() {
let (overseer, _handler): (Xxx<_>, _) = Xxx::builder()
.sub0(AwesomeSubSys::default())
.plinkos(GoblinTower::default())
.i_like_pi(::std::f64::consts::PI)
.spawner(DummySpawner)
.build()
.unwrap();
assert_eq!(overseer.i_like_pi.floor() as i8, 3);
}
@@ -0,0 +1,21 @@
[package]
name = "polkadot-overseer-gen-proc-macro"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
description = "Generate an overseer including builder pattern and message wrapper from a single annotated struct definition."
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
quote = "1.0.9"
proc-macro2 = "1.0.26"
proc-macro-crate = "1.0.0"
[dev-dependencies]
assert_matches = "1.5.0"
@@ -0,0 +1,373 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use quote::quote;
use syn::Ident;
use super::*;
/// Implement a builder pattern for the `Overseer`-type,
/// which acts as the gateway to constructing the overseer.
///
/// Elements tagged with `wip` are not covered here.
pub(crate) fn impl_builder(info: &OverseerInfo) -> proc_macro2::TokenStream {
let overseer_name = info.overseer_name.clone();
let builder = Ident::new(&(overseer_name.to_string() + "Builder"), overseer_name.span());
let handle = Ident::new(&(overseer_name.to_string() + "Handle"), overseer_name.span());
let subsystem_name = &info.subsystem_names_without_wip();
let builder_generic_ty = &info.builder_generic_types();
let channel_name = &info.channel_names_without_wip("");
let channel_name_unbounded = &info.channel_names_without_wip("_unbounded");
let channel_name_tx = &info.channel_names_without_wip("_tx");
let channel_name_unbounded_tx = &info.channel_names_without_wip("_unbounded_tx");
let channel_name_rx = &info.channel_names_without_wip("_rx");
let channel_name_unbounded_rx = &info.channel_names_without_wip("_unbounded_rx");
let baggage_generic_ty = &info.baggage_generic_types();
let baggage_name = &info.baggage_names();
let baggage_ty = &info.baggage_types();
let error_ty = &info.extern_error_ty;
let support_crate = info.support_crate_name();
let blocking = &info
.subsystems()
.iter()
.map(|x| {
if x.blocking {
quote! { Blocking }
} else {
quote! { Regular }
}
})
.collect::<Vec<_>>();
let generics = quote! {
< S, #( #baggage_generic_ty, )* >
};
let where_clause = quote! {
where
S: #support_crate ::SpawnNamed,
};
let builder_generics = quote! {
<S, #( #baggage_generic_ty, )* #( #builder_generic_ty, )* >
};
// all subsystems must have the same context
// even if the overseer does not impose such a limit.
let builder_additional_generics = quote! {
<#( #builder_generic_ty, )* >
};
let consumes = &info.consumes();
let subsyste_ctx_name = Ident::new(
&(overseer_name.to_string() + "SubsystemContext"),
overseer_name.span()
);
let builder_where_clause = quote! {
where
S: #support_crate ::SpawnNamed,
#(
#builder_generic_ty : Subsystem<#subsyste_ctx_name< #consumes >, #error_ty>,
)*
};
let event = &info.extern_event_ty;
let mut ts = quote! {
impl #generics #overseer_name #generics #where_clause {
/// Create a new overseer utilizing the builder.
pub fn builder #builder_additional_generics () -> #builder #builder_generics
#builder_where_clause
{
#builder :: default()
}
}
/// Handle for an overseer.
pub type #handle = #support_crate ::metered::MeteredSender< #event >;
#[allow(missing_docs)]
pub struct #builder #builder_generics {
#(
#subsystem_name : ::std::option::Option< #builder_generic_ty >,
)*
#(
#baggage_name : ::std::option::Option< #baggage_ty >,
)*
spawner: ::std::option::Option< S >,
}
impl #builder_generics Default for #builder #builder_generics {
fn default() -> Self {
// explicitly assure the required traits are implemented
fn trait_from_must_be_implemented<E>()
where
E: std::error::Error + Send + Sync + 'static + From<#support_crate ::OverseerError>
{}
trait_from_must_be_implemented::< #error_ty >();
Self {
#(
#subsystem_name: None,
)*
#(
#baggage_name: None,
)*
spawner: None,
}
}
}
impl #builder_generics #builder #builder_generics #builder_where_clause {
/// The spawner to use for spawning tasks.
pub fn spawner(mut self, spawner: S) -> Self
where
S: #support_crate ::SpawnNamed + Send
{
self.spawner = Some(spawner);
self
}
#(
/// Specify the particular subsystem implementation.
pub fn #subsystem_name (mut self, subsystem: #builder_generic_ty ) -> Self {
self. #subsystem_name = Some( subsystem );
self
}
)*
#(
/// Attach the user defined addendum type.
pub fn #baggage_name (mut self, baggage: #baggage_ty ) -> Self {
self. #baggage_name = Some( baggage );
self
}
)*
/// Complete the construction and create the overseer type.
pub fn build(mut self) -> ::std::result::Result<(#overseer_name #generics, #handle), #error_ty>
{
let (events_tx, events_rx) = #support_crate ::metered::channel::<
#event
>(SIGNAL_CHANNEL_CAPACITY);
let handle: #handle = events_tx.clone();
let (to_overseer_tx, to_overseer_rx) = #support_crate ::metered::unbounded::<
ToOverseer
>();
#(
let (#channel_name_tx, #channel_name_rx)
=
#support_crate ::metered::channel::<
MessagePacket< #consumes >
>(CHANNEL_CAPACITY);
)*
#(
let (#channel_name_unbounded_tx, #channel_name_unbounded_rx) =
#support_crate ::metered::unbounded::<
MessagePacket< #consumes >
>();
)*
let channels_out =
ChannelsOut {
#(
#channel_name: #channel_name_tx .clone(),
)*
#(
#channel_name_unbounded: #channel_name_unbounded_tx,
)*
};
let mut spawner = self.spawner.expect("Spawner is set. qed");
let mut running_subsystems = #support_crate ::FuturesUnordered::<
BoxFuture<'static, ::std::result::Result<(), #error_ty > >
>::new();
#(
// TODO generate a builder pattern that ensures this
// TODO https://github.com/paritytech/polkadot/issues/3427
let #subsystem_name = self. #subsystem_name .expect("All subsystem must exist with the builder pattern.");
let unbounded_meter = #channel_name_unbounded_rx.meter().clone();
let message_rx: SubsystemIncomingMessages< #consumes > = #support_crate ::select(
#channel_name_rx, #channel_name_unbounded_rx
);
let (signal_tx, signal_rx) = #support_crate ::metered::channel(SIGNAL_CHANNEL_CAPACITY);
let ctx = #subsyste_ctx_name::< #consumes >::new(
signal_rx,
message_rx,
channels_out.clone(),
to_overseer_tx.clone(),
);
let #subsystem_name: OverseenSubsystem< #consumes > =
spawn::<_,_, #blocking, _, _, _>(
&mut spawner,
#channel_name_tx,
signal_tx,
unbounded_meter,
channels_out.clone(),
ctx,
#subsystem_name,
&mut running_subsystems,
)?;
)*
#(
let #baggage_name = self. #baggage_name .expect(
&format!("Baggage variable `{1}` of `{0}` ",
stringify!(#overseer_name),
stringify!( #baggage_name )
)
);
)*
use #support_crate ::StreamExt;
let to_overseer_rx = to_overseer_rx.fuse();
let overseer = #overseer_name {
#(
#subsystem_name,
)*
#(
#baggage_name,
)*
spawner,
running_subsystems,
events_rx,
to_overseer_rx,
};
Ok((overseer, handle))
}
}
};
ts.extend(impl_task_kind(info));
ts
}
pub(crate) fn impl_task_kind(info: &OverseerInfo) -> proc_macro2::TokenStream {
let signal = &info.extern_signal_ty;
let error_ty = &info.extern_error_ty;
let support_crate = info.support_crate_name();
let ts = quote! {
use #support_crate ::FutureExt as _;
/// Task kind to launch.
pub trait TaskKind {
/// Spawn a task, it depends on the implementer if this is blocking or not.
fn launch_task<S: SpawnNamed>(spawner: &mut S, name: &'static str, future: BoxFuture<'static, ()>);
}
#[allow(missing_docs)]
struct Regular;
impl TaskKind for Regular {
fn launch_task<S: SpawnNamed>(spawner: &mut S, name: &'static str, future: BoxFuture<'static, ()>) {
spawner.spawn(name, future)
}
}
#[allow(missing_docs)]
struct Blocking;
impl TaskKind for Blocking {
fn launch_task<S: SpawnNamed>(spawner: &mut S, name: &'static str, future: BoxFuture<'static, ()>) {
spawner.spawn_blocking(name, future)
}
}
/// Spawn task of kind `self` using spawner `S`.
pub fn spawn<S, M, TK, Ctx, E, SubSys>(
spawner: &mut S,
message_tx: #support_crate ::metered::MeteredSender<MessagePacket<M>>,
signal_tx: #support_crate ::metered::MeteredSender< #signal >,
// meter for the unbounded channel
unbounded_meter: #support_crate ::metered::Meter,
// connection to the subsystems
channels_out: ChannelsOut,
ctx: Ctx,
s: SubSys,
futures: &mut #support_crate ::FuturesUnordered<BoxFuture<'static, ::std::result::Result<(), #error_ty> >>,
) -> ::std::result::Result<OverseenSubsystem<M>, #error_ty >
where
S: #support_crate ::SpawnNamed,
M: std::fmt::Debug + Send + 'static,
TK: TaskKind,
Ctx: #support_crate ::SubsystemContext<Message=M>,
E: std::error::Error + Send + Sync + 'static + From<#support_crate ::OverseerError>,
SubSys: #support_crate ::Subsystem<Ctx, E>,
{
let #support_crate ::SpawnedSubsystem::<E> { future, name } = s.start(ctx);
let (tx, rx) = #support_crate ::oneshot::channel();
let fut = Box::pin(async move {
if let Err(e) = future.await {
#support_crate ::tracing::error!(subsystem=name, err = ?e, "subsystem exited with error");
} else {
#support_crate ::tracing::debug!(subsystem=name, "subsystem exited without an error");
}
let _ = tx.send(());
});
<TK as TaskKind>::launch_task(spawner, name, fut);
futures.push(Box::pin(
rx.map(|e| {
tracing::warn!(err = ?e, "dropping error");
Ok(())
})
));
let instance = Some(SubsystemInstance {
meters: #support_crate ::SubsystemMeters {
unbounded: unbounded_meter,
bounded: message_tx.meter().clone(),
signals: signal_tx.meter().clone(),
},
tx_signal: signal_tx,
tx_bounded: message_tx,
signals_received: 0,
name,
});
Ok(OverseenSubsystem {
instance,
})
}
};
ts
}
@@ -0,0 +1,128 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use quote::quote;
use syn::Result;
use super::*;
/// Implement the helper type `ChannelsOut` and `MessagePacket<T>`.
pub(crate) fn impl_channels_out_struct(info: &OverseerInfo) -> Result<proc_macro2::TokenStream> {
let message_wrapper = info.message_wrapper.clone();
let channel_name = &info.channel_names_without_wip("");
let channel_name_unbounded = &info.channel_names_without_wip("_unbounded");
let consumes = &info.consumes_without_wip();
let consumes_variant = &info.variant_names_without_wip();
let unconsumes_variant = &info.variant_names_only_wip();
let support_crate = info.support_crate_name();
let ts = quote! {
/// Collection of channels to the individual subsystems.
///
/// Naming is from the point of view of the overseer.
#[derive(Debug, Clone)]
pub struct ChannelsOut {
#(
/// Bounded channel sender, connected to a subsystem.
pub #channel_name:
#support_crate ::metered::MeteredSender<
MessagePacket< #consumes >
>,
)*
#(
/// Unbounded channel sender, connected to a subsystem.
pub #channel_name_unbounded:
#support_crate ::metered::UnboundedMeteredSender<
MessagePacket< #consumes >
>,
)*
}
impl ChannelsOut {
/// Send a message via a bounded channel.
pub async fn send_and_log_error(
&mut self,
signals_received: usize,
message: #message_wrapper,
) {
let res: ::std::result::Result<_, _> = match message {
#(
#message_wrapper :: #consumes_variant ( inner ) => {
self. #channel_name .send(
#support_crate ::make_packet(signals_received, inner)
).await.map_err(|_| stringify!( #channel_name ))
}
)*
// subsystems that are wip
#(
#message_wrapper :: #unconsumes_variant ( _ ) => Ok(()),
)*
// dummy message type
#message_wrapper :: Empty => Ok(()),
};
if let Err(subsystem_name) = res {
#support_crate ::tracing::debug!(
target: LOG_TARGET,
"Failed to send (bounded) a message to {} subsystem",
subsystem_name
);
}
}
/// Send a message to another subsystem via an unbounded channel.
pub fn send_unbounded_and_log_error(
&self,
signals_received: usize,
message: #message_wrapper,
) {
use ::std::sync::mpsc::TrySendError;
let res: ::std::result::Result<_, _> = match message {
#(
#message_wrapper :: #consumes_variant (inner) => {
self. #channel_name_unbounded .unbounded_send(
#support_crate ::make_packet(signals_received, inner)
)
.map_err(|_| stringify!( #channel_name ))
},
)*
// subsystems that are wip
#(
#message_wrapper :: #unconsumes_variant ( _ ) => Ok(()),
)*
// dummy message type
#message_wrapper :: Empty => Ok(())
};
if let Err(subsystem_name) = res {
#support_crate ::tracing::debug!(
target: LOG_TARGET,
"Failed to send_unbounded a message to {} subsystem",
subsystem_name
);
}
}
}
};
Ok(ts)
}
@@ -0,0 +1,70 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use proc_macro2::{TokenStream, Ident};
use quote::quote;
use syn::Path;
pub(crate) fn impl_dispatch(info: &OverseerInfo) -> TokenStream {
let message_wrapper = &info.message_wrapper;
let dispatchable_variant = info
.subsystems()
.into_iter()
.filter(|ssf| !ssf.no_dispatch)
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.generic.clone())
.collect::<Vec<Ident>>();
let dispatchable_message = info
.subsystems()
.into_iter()
.filter(|ssf| !ssf.no_dispatch)
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.consumes.clone())
.collect::<Vec<Path>>();
let mut ts = TokenStream::new();
if let Some(extern_network_ty) = &info.extern_network_ty.clone() {
ts.extend(quote! {
impl #message_wrapper {
/// Generated dispatch iterator generator.
pub fn dispatch_iter(extern_msg: #extern_network_ty) -> impl Iterator<Item=Self> + Send {
::std::array::IntoIter::new([
#(
extern_msg
// focuses on a `NetworkBridgeEvent< protocol_v1::* >`
// TODO do not require this to be hardcoded, either externalize or ...
// https://github.com/paritytech/polkadot/issues/3427
.focus()
.ok()
.map(|event| {
#message_wrapper :: #dispatchable_variant (
// the inner type of the enum variant
#dispatchable_message :: from( event )
)
}),
)*
])
.into_iter()
.filter_map(|x: Option<_>| x)
}
}
});
}
ts
}
@@ -0,0 +1,83 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use quote::quote;
use syn::Result;
use syn::spanned::Spanned;
use super::*;
/// Generates the wrapper type enum.
pub(crate) fn impl_message_wrapper_enum(info: &OverseerInfo) -> Result<proc_macro2::TokenStream> {
let consumes = info.consumes();
let consumes_variant = info.variant_names();
let outgoing = &info.outgoing_ty;
let message_wrapper = &info.message_wrapper;
let (outgoing_from_impl, outgoing_decl) = if let Some(outgoing) = outgoing {
let outgoing_variant = outgoing
.get_ident()
.ok_or_else(||{
syn::Error::new(outgoing.span(), "Missing identifier to use as enum variant for outgoing.")
})?;
(quote! {
impl ::std::convert::From< #outgoing > for #message_wrapper {
fn from(message: #outgoing) -> Self {
#message_wrapper :: #outgoing_variant ( message )
}
}
},
quote! {
#outgoing_variant ( #outgoing ) ,
})
} else {
(TokenStream::new(), TokenStream::new())
};
let ts = quote! {
/// Generated message type wrapper
#[allow(missing_docs)]
#[derive(Debug)]
pub enum #message_wrapper {
#(
#consumes_variant ( #consumes ),
)*
#outgoing_decl
// dummy message type
Empty,
}
impl ::std::convert::From< () > for #message_wrapper {
fn from(_: ()) -> Self {
#message_wrapper :: Empty
}
}
#(
impl ::std::convert::From< #consumes > for #message_wrapper {
fn from(message: #consumes) -> Self {
#message_wrapper :: #consumes_variant ( message )
}
}
)*
#outgoing_from_impl
};
Ok(ts)
}
@@ -0,0 +1,248 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use quote::quote;
use syn::Ident;
use super::*;
/// Implement a builder pattern for the `Overseer`-type,
/// which acts as the gateway to constructing the overseer.
pub(crate) fn impl_misc(info: &OverseerInfo) -> proc_macro2::TokenStream {
let overseer_name = info.overseer_name.clone();
let subsystem_sender_name = Ident::new(&(overseer_name.to_string() + "SubsystemSender"), overseer_name.span());
let subsystem_ctx_name = Ident::new(&(overseer_name.to_string() + "SubsystemContext"), overseer_name.span());
let consumes = &info.consumes();
let signal = &info.extern_signal_ty;
let wrapper_message = &info.message_wrapper;
let error_ty = &info.extern_error_ty;
let support_crate = info.support_crate_name();
let ts = quote! {
/// Connector to send messages towards all subsystems,
/// while tracking the which signals where already received.
#[derive(Debug, Clone)]
pub struct #subsystem_sender_name {
/// Collection of channels to all subsystems.
channels: ChannelsOut,
/// Systemwide tick for which signals were received by all subsystems.
signals_received: SignalsReceived,
}
/// impl for wrapping message type...
#[#support_crate ::async_trait]
impl SubsystemSender< #wrapper_message > for #subsystem_sender_name {
async fn send_message(&mut self, msg: #wrapper_message) {
self.channels.send_and_log_error(self.signals_received.load(), msg).await;
}
async fn send_messages<T>(&mut self, msgs: T)
where
T: IntoIterator<Item = #wrapper_message> + Send,
T::IntoIter: Send,
{
// This can definitely be optimized if necessary.
for msg in msgs {
self.send_message(msg).await;
}
}
fn send_unbounded_message(&mut self, msg: #wrapper_message) {
self.channels.send_unbounded_and_log_error(self.signals_received.load(), msg);
}
}
// ... but also implement for all individual messages to avoid
// the necessity for manual wrapping, and do the conversion
// based on the generated `From::from` impl for the individual variants.
#(
#[#support_crate ::async_trait]
impl SubsystemSender< #consumes > for #subsystem_sender_name {
async fn send_message(&mut self, msg: #consumes) {
self.channels.send_and_log_error(self.signals_received.load(), #wrapper_message ::from ( msg )).await;
}
async fn send_messages<T>(&mut self, msgs: T)
where
T: IntoIterator<Item = #consumes> + Send,
T::IntoIter: Send,
{
// This can definitely be optimized if necessary.
for msg in msgs {
self.send_message(msg).await;
}
}
fn send_unbounded_message(&mut self, msg: #consumes) {
self.channels.send_unbounded_and_log_error(self.signals_received.load(), #wrapper_message ::from ( msg ));
}
}
)*
/// A context type that is given to the [`Subsystem`] upon spawning.
/// It can be used by [`Subsystem`] to communicate with other [`Subsystem`]s
/// or to spawn it's [`SubsystemJob`]s.
///
/// [`Overseer`]: struct.Overseer.html
/// [`Subsystem`]: trait.Subsystem.html
/// [`SubsystemJob`]: trait.SubsystemJob.html
#[derive(Debug)]
#[allow(missing_docs)]
pub struct #subsystem_ctx_name<M>{
signals: #support_crate ::metered::MeteredReceiver< #signal >,
messages: SubsystemIncomingMessages<M>,
to_subsystems: #subsystem_sender_name,
to_overseer: #support_crate ::metered::UnboundedMeteredSender<
#support_crate ::ToOverseer
>,
signals_received: SignalsReceived,
pending_incoming: Option<(usize, M)>,
}
impl<M> #subsystem_ctx_name<M> {
/// Create a new context.
fn new(
signals: #support_crate ::metered::MeteredReceiver< #signal >,
messages: SubsystemIncomingMessages<M>,
to_subsystems: ChannelsOut,
to_overseer: #support_crate ::metered::UnboundedMeteredSender<ToOverseer>,
) -> Self {
let signals_received = SignalsReceived::default();
#subsystem_ctx_name {
signals,
messages,
to_subsystems: #subsystem_sender_name {
channels: to_subsystems,
signals_received: signals_received.clone(),
},
to_overseer,
signals_received,
pending_incoming: None,
}
}
}
#[#support_crate ::async_trait]
impl<M: std::fmt::Debug + Send + 'static> SubsystemContext for #subsystem_ctx_name<M>
where
#subsystem_sender_name: #support_crate ::SubsystemSender< #wrapper_message >,
#wrapper_message: From<M>,
{
type Message = M;
type Signal = #signal;
type Sender = #subsystem_sender_name;
type AllMessages = #wrapper_message;
type Error = #error_ty;
async fn try_recv(&mut self) -> ::std::result::Result<Option<FromOverseer<M, #signal>>, ()> {
match #support_crate ::poll!(self.recv()) {
#support_crate ::Poll::Ready(msg) => Ok(Some(msg.map_err(|_| ())?)),
#support_crate ::Poll::Pending => Ok(None),
}
}
async fn recv(&mut self) -> ::std::result::Result<FromOverseer<M, #signal>, #error_ty> {
loop {
// If we have a message pending an overseer signal, we only poll for signals
// in the meantime.
if let Some((needs_signals_received, msg)) = self.pending_incoming.take() {
if needs_signals_received <= self.signals_received.load() {
return Ok(#support_crate ::FromOverseer::Communication { msg });
} else {
self.pending_incoming = Some((needs_signals_received, msg));
// wait for next signal.
let signal = self.signals.next().await
.ok_or(#support_crate ::OverseerError::Context(
"Signal channel is terminated and empty."
.to_owned()
))?;
self.signals_received.inc();
return Ok(#support_crate ::FromOverseer::Signal(signal))
}
}
let mut await_message = self.messages.next().fuse();
let mut await_signal = self.signals.next().fuse();
let signals_received = self.signals_received.load();
let pending_incoming = &mut self.pending_incoming;
// Otherwise, wait for the next signal or incoming message.
let from_overseer = #support_crate ::futures::select_biased! {
signal = await_signal => {
let signal = signal
.ok_or(#support_crate ::OverseerError::Context(
"Signal channel is terminated and empty."
.to_owned()
))?;
#support_crate ::FromOverseer::Signal(signal)
}
msg = await_message => {
let packet = msg
.ok_or(#support_crate ::OverseerError::Context(
"Message channel is terminated and empty."
.to_owned()
))?;
if packet.signals_received > signals_received {
// wait until we've received enough signals to return this message.
*pending_incoming = Some((packet.signals_received, packet.message));
continue;
} else {
// we know enough to return this message.
#support_crate ::FromOverseer::Communication { msg: packet.message}
}
}
};
if let #support_crate ::FromOverseer::Signal(_) = from_overseer {
self.signals_received.inc();
}
return Ok(from_overseer);
}
}
fn sender(&mut self) -> &mut Self::Sender {
&mut self.to_subsystems
}
fn spawn(&mut self, name: &'static str, s: Pin<Box<dyn Future<Output = ()> + Send>>)
-> ::std::result::Result<(), #error_ty>
{
self.to_overseer.unbounded_send(#support_crate ::ToOverseer::SpawnJob {
name,
s,
}).map_err(|_| #support_crate ::OverseerError::TaskSpawn(name))?;
Ok(())
}
fn spawn_blocking(&mut self, name: &'static str, s: Pin<Box<dyn Future<Output = ()> + Send>>)
-> ::std::result::Result<(), #error_ty>
{
self.to_overseer.unbounded_send(#support_crate ::ToOverseer::SpawnBlockingJob {
name,
s,
}).map_err(|_| #support_crate ::OverseerError::TaskSpawn(name))?;
Ok(())
}
}
};
ts
}
@@ -0,0 +1,265 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use quote::quote;
use super::*;
pub(crate) fn impl_overseer_struct(info: &OverseerInfo) -> proc_macro2::TokenStream {
let message_wrapper = &info.message_wrapper.clone();
let overseer_name = info.overseer_name.clone();
let subsystem_name = &info.subsystem_names_without_wip();
let support_crate = info.support_crate_name();
let baggage_decl = &info.baggage_decl();
let baggage_generic_ty = &info.baggage_generic_types();
let generics = quote! {
< S, #( #baggage_generic_ty, )* >
};
let where_clause = quote! {
where
S: #support_crate ::SpawnNamed,
};
// TODO add `where ..` clauses for baggage types
// TODO https://github.com/paritytech/polkadot/issues/3427
let consumes = &info.consumes_without_wip();
let consumes_variant = &info.variant_names_without_wip();
let unconsumes_variant = &info.variant_names_only_wip();
let signal_ty = &info.extern_signal_ty;
let error_ty = &info.extern_error_ty;
let event_ty = &info.extern_event_ty;
let message_channel_capacity = info.message_channel_capacity;
let signal_channel_capacity = info.signal_channel_capacity;
let log_target = syn::LitStr::new(overseer_name.to_string().to_lowercase().as_str(), overseer_name.span());
let ts = quote! {
const STOP_DELAY: ::std::time::Duration = ::std::time::Duration::from_secs(1);
/// Capacity of a bounded message channel between overseer and subsystem
/// but also for bounded channels between two subsystems.
const CHANNEL_CAPACITY: usize = #message_channel_capacity;
/// Capacity of a signal channel between a subsystem and the overseer.
const SIGNAL_CHANNEL_CAPACITY: usize = #signal_channel_capacity;
/// The log target tag.
const LOG_TARGET: &'static str = #log_target;
/// The overseer.
pub struct #overseer_name #generics {
#(
/// A subsystem instance.
#subsystem_name: OverseenSubsystem< #consumes >,
)*
#(
/// A user specified addendum field.
#baggage_decl ,
)*
/// Responsible for driving the subsystem futures.
spawner: S,
/// The set of running subsystems.
running_subsystems: #support_crate ::FuturesUnordered<
BoxFuture<'static, ::std::result::Result<(), #error_ty>>
>,
/// Gather running subsystems' outbound streams into one.
to_overseer_rx: #support_crate ::stream::Fuse<
#support_crate ::metered::UnboundedMeteredReceiver< ToOverseer >
>,
/// Events that are sent to the overseer from the outside world.
events_rx: #support_crate ::metered::MeteredReceiver< #event_ty >,
}
impl #generics #overseer_name #generics #where_clause {
/// Send the given signal, a terminatin signal, to all subsystems
/// and wait for all subsystems to go down.
///
/// The definition of a termination signal is up to the user and
/// implementation specific.
pub async fn wait_terminate(&mut self, signal: #signal_ty, timeout: ::std::time::Duration) -> ::std::result::Result<(), #error_ty > {
#(
::std::mem::drop(self. #subsystem_name .send_signal(signal.clone()).await);
)*
let _ = signal;
let mut timeout_fut = #support_crate ::Delay::new(
timeout
).fuse();
loop {
select! {
_ = self.running_subsystems.next() => {
if self.running_subsystems.is_empty() {
break;
}
},
_ = timeout_fut => break,
complete => break,
}
}
Ok(())
}
/// Broadcast a signal to all subsystems.
pub async fn broadcast_signal(&mut self, signal: #signal_ty) -> ::std::result::Result<(), #error_ty > {
#(
let _ = self. #subsystem_name .send_signal(signal.clone()).await;
)*
let _ = signal;
Ok(())
}
/// Route a particular message to a subsystem that consumes the message.
pub async fn route_message(&mut self, message: #message_wrapper, origin: &'static str) -> ::std::result::Result<(), #error_ty > {
match message {
#(
#message_wrapper :: #consumes_variant ( inner ) =>
OverseenSubsystem::< #consumes >::send_message2(&mut self. #subsystem_name, inner, origin ).await?,
)*
// subsystems that are still work in progress
#(
#message_wrapper :: #unconsumes_variant ( _ ) => {}
)*
#message_wrapper :: Empty => {}
}
Ok(())
}
/// Extract information from each subsystem.
pub fn map_subsystems<'a, Mapper, Output>(&'a self, mapper: Mapper)
-> Vec<Output>
where
#(
Mapper: MapSubsystem<&'a OverseenSubsystem< #consumes >, Output=Output>,
)*
{
vec![
#(
mapper.map_subsystem( & self. #subsystem_name ),
)*
]
}
/// Get access to internal task spawner.
pub fn spawner<'a> (&'a mut self) -> &'a mut S {
&mut self.spawner
}
}
};
ts
}
pub(crate) fn impl_overseen_subsystem(info: &OverseerInfo) -> proc_macro2::TokenStream {
let signal = &info.extern_signal_ty;
let error_ty = &info.extern_error_ty;
let support_crate = info.support_crate_name();
let ts = quote::quote! {
use #support_crate ::futures::SinkExt as _;
/// A subsystem that the overseer oversees.
///
/// Ties together the [`Subsystem`] itself and it's running instance
/// (which may be missing if the [`Subsystem`] is not running at the moment
/// for whatever reason).
///
/// [`Subsystem`]: trait.Subsystem.html
pub struct OverseenSubsystem<M> {
/// The instance.
pub instance: std::option::Option<
#support_crate ::SubsystemInstance<M, #signal>
>,
}
impl<M> OverseenSubsystem<M> {
/// Send a message to the wrapped subsystem.
///
/// If the inner `instance` is `None`, nothing is happening.
pub async fn send_message2(&mut self, message: M, origin: &'static str) -> ::std::result::Result<(), #error_ty > {
const MESSAGE_TIMEOUT: Duration = Duration::from_secs(10);
if let Some(ref mut instance) = self.instance {
match instance.tx_bounded.send(MessagePacket {
signals_received: instance.signals_received,
message: message.into(),
}).timeout(MESSAGE_TIMEOUT).await
{
None => {
#support_crate ::tracing::error!(
target: LOG_TARGET,
%origin,
"Subsystem {} appears unresponsive.",
instance.name,
);
Err(#error_ty :: from(
#support_crate ::OverseerError::SubsystemStalled(instance.name)
))
}
Some(res) => res.map_err(Into::into),
}
} else {
Ok(())
}
}
/// Send a signal to the wrapped subsystem.
///
/// If the inner `instance` is `None`, nothing is happening.
pub async fn send_signal(&mut self, signal: #signal) -> ::std::result::Result<(), #error_ty > {
const SIGNAL_TIMEOUT: ::std::time::Duration = ::std::time::Duration::from_secs(10);
if let Some(ref mut instance) = self.instance {
match instance.tx_signal.send(signal).timeout(SIGNAL_TIMEOUT).await {
None => {
Err(#error_ty :: from(
#support_crate ::OverseerError::SubsystemStalled(instance.name)
))
}
Some(res) => {
let res = res.map_err(Into::into);
if res.is_ok() {
instance.signals_received += 1;
}
res
}
}
} else {
Ok(())
}
}
}
};
ts
}
@@ -0,0 +1,94 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
#![deny(unused_crate_dependencies)]
use proc_macro2::{Span, Ident, TokenStream};
use syn::{parse2, Result};
use quote::{quote, ToTokens};
mod impl_builder;
mod impl_misc;
mod impl_overseer;
mod parse_attr;
mod parse_struct;
mod impl_channels_out;
mod impl_dispatch;
mod impl_message_wrapper;
use impl_builder::*;
use impl_channels_out::*;
use impl_dispatch::*;
use impl_message_wrapper::*;
use impl_misc::*;
use impl_overseer::*;
use parse_attr::*;
use parse_struct::*;
#[cfg(test)]
mod tests;
#[proc_macro_attribute]
pub fn overlord(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
impl_overseer_gen(attr, item).unwrap_or_else(|err| err.to_compile_error()).into()
}
pub(crate) fn impl_overseer_gen(attr: TokenStream, orig: TokenStream) -> Result<proc_macro2::TokenStream> {
let args: AttrArgs = parse2(attr)?;
let message_wrapper = args.message_wrapper;
let of: OverseerGuts = parse2(orig)?;
let support_crate_name = if cfg!(test) {
quote!{crate}
} else {
use proc_macro_crate::{crate_name, FoundCrate};
let crate_name = crate_name("polkadot-overseer-gen")
.expect("Support crate polkadot-overseer-gen is present in `Cargo.toml`. qed");
match crate_name {
FoundCrate::Itself => quote!{crate},
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()).to_token_stream(),
}
};
let info = OverseerInfo {
support_crate_name,
subsystems: of.subsystems,
baggage: of.baggage,
overseer_name: of.name,
message_wrapper,
message_channel_capacity: args.message_channel_capacity,
signal_channel_capacity: args.signal_channel_capacity,
extern_event_ty: args.extern_event_ty,
extern_signal_ty: args.extern_signal_ty,
extern_error_ty: args.extern_error_ty,
extern_network_ty: args.extern_network_ty,
outgoing_ty: args.outgoing_ty,
};
let mut additive = impl_overseer_struct(&info);
additive.extend(impl_builder(&info));
additive.extend(impl_overseen_subsystem(&info));
additive.extend(impl_channels_out_struct(&info));
additive.extend(impl_misc(&info));
additive.extend(impl_message_wrapper_enum(&info)?);
additive.extend(impl_dispatch(&info));
Ok(additive)
}
@@ -0,0 +1,227 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use proc_macro2::Span;
use std::collections::{hash_map::RandomState, HashMap};
use syn::parse::{Parse, ParseBuffer};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::{Error, Ident, LitInt, Path, Result, Token};
use quote::{quote, ToTokens};
mod kw {
syn::custom_keyword!(event);
syn::custom_keyword!(signal);
syn::custom_keyword!(error);
syn::custom_keyword!(network);
syn::custom_keyword!(outgoing);
syn::custom_keyword!(gen);
syn::custom_keyword!(signal_capacity);
syn::custom_keyword!(message_capacity);
}
#[derive(Clone, Debug)]
enum OverseerAttrItem {
ExternEventType {
tag: kw::event,
eq_token: Token![=],
value: Path
},
ExternNetworkType {
tag: kw::network,
eq_token: Token![=],
value: Path
},
ExternOverseerSignalType {
tag: kw::signal,
eq_token: Token![=],
value: Path
},
ExternErrorType {
tag: kw::error,
eq_token: Token![=],
value: Path
},
OutgoingType {
tag: kw::outgoing,
eq_token: Token![=],
value: Path
},
MessageWrapperName {
tag: kw::gen,
eq_token: Token![=],
value: Ident
},
SignalChannelCapacity {
tag: kw::signal_capacity,
eq_token: Token![=],
value: usize
},
MessageChannelCapacity {
tag: kw::message_capacity,
eq_token: Token![=],
value: usize
},
}
impl ToTokens for OverseerAttrItem {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ts = match self {
Self::ExternEventType { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::ExternNetworkType { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::ExternOverseerSignalType { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::ExternErrorType { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::OutgoingType { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::MessageWrapperName { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::SignalChannelCapacity { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
Self::MessageChannelCapacity { tag, eq_token, value } => { quote!{ #tag #eq_token, #value } }
};
tokens.extend(ts.into_iter());
}
}
impl Parse for OverseerAttrItem {
fn parse(input: &ParseBuffer) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::event) {
Ok(OverseerAttrItem::ExternEventType {
tag: input.parse::<kw::event>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::signal) {
Ok(OverseerAttrItem::ExternOverseerSignalType {
tag: input.parse::<kw::signal>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::error) {
Ok(OverseerAttrItem::ExternErrorType {
tag: input.parse::<kw::error>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::network) {
Ok(OverseerAttrItem::ExternNetworkType {
tag: input.parse::<kw::network>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::outgoing) {
Ok(OverseerAttrItem::OutgoingType {
tag: input.parse::<kw::outgoing>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::gen) {
Ok(OverseerAttrItem::MessageWrapperName {
tag: input.parse::<kw::gen>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::signal_capacity) {
Ok(OverseerAttrItem::SignalChannelCapacity {
tag: input.parse::<kw::signal_capacity>()?,
eq_token: input.parse()?,
value: input.parse::<LitInt>()?.base10_parse::<usize>()?
})
} else if lookahead.peek(kw::message_capacity) {
Ok(OverseerAttrItem::MessageChannelCapacity {
tag: input.parse::<kw::message_capacity>()?,
eq_token: input.parse()?,
value: input.parse::<LitInt>()?.base10_parse::<usize>()?,
})
} else {
Err(lookahead.error())
}
}
}
/// Attribute arguments
#[derive(Clone, Debug)]
pub(crate) struct AttrArgs {
pub(crate) message_wrapper: Ident,
pub(crate) extern_event_ty: Path,
pub(crate) extern_signal_ty: Path,
pub(crate) extern_error_ty: Path,
/// A external subsystem that both consumes and produces messages
/// but is not part of the band of subsystems, it's a mere proxy
/// to another entity that consumes/produces messages.
pub(crate) extern_network_ty: Option<Path>,
pub(crate) outgoing_ty: Option<Path>,
pub(crate) signal_channel_capacity: usize,
pub(crate) message_channel_capacity: usize,
}
macro_rules! extract_variant {
($unique:expr, $variant:ident ; default = $fallback:expr) => {
extract_variant!($unique, $variant)
.unwrap_or_else(|| { $fallback })
};
($unique:expr, $variant:ident ; err = $err:expr) => {
extract_variant!($unique, $variant)
.ok_or_else(|| {
Error::new(Span::call_site(), $err)
})
};
($unique:expr, $variant:ident) => {
$unique.values()
.find_map(|item| {
if let OverseerAttrItem:: $variant { value, ..} = item {
Some(value.clone())
} else {
None
}
})
};
}
impl Parse for AttrArgs {
fn parse(input: &ParseBuffer) -> Result<Self> {
let items: Punctuated<OverseerAttrItem, Token![,]> = input.parse_terminated(OverseerAttrItem::parse)?;
let mut unique = HashMap::<std::mem::Discriminant<OverseerAttrItem>, OverseerAttrItem, RandomState>::default();
for item in items {
if let Some(first) = unique.insert(std::mem::discriminant(&item), item.clone()) {
let mut e = Error::new(item.span(), format!("Duplicate definition of overseer generation type found"));
e.combine(Error::new(first.span(), "previously defined here."));
return Err(e);
}
}
let signal_channel_capacity = extract_variant!(unique, SignalChannelCapacity; default = 64_usize);
let message_channel_capacity = extract_variant!(unique, MessageChannelCapacity; default = 1024_usize);
let error = extract_variant!(unique, ExternErrorType; err = "Must declare the overseer error type via `error=..`.")?;
let event = extract_variant!(unique, ExternEventType; err = "Must declare the overseer event type via `event=..`.")?;
let signal = extract_variant!(unique, ExternOverseerSignalType; err = "Must declare the overseer signal type via `span=..`.")?;
let message_wrapper = extract_variant!(unique, MessageWrapperName; err = "Must declare the overseer generated wrapping message type via `gen=..`.")?;
let network = extract_variant!(unique, ExternNetworkType);
let outgoing = extract_variant!(unique, OutgoingType);
Ok(AttrArgs {
signal_channel_capacity,
message_channel_capacity,
extern_event_ty: event,
extern_signal_ty: signal,
extern_error_ty: error,
extern_network_ty: network,
outgoing_ty: outgoing,
message_wrapper,
})
}
}
@@ -0,0 +1,431 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use proc_macro2::{Span, TokenStream};
use std::collections::{hash_map::RandomState, HashSet, HashMap};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::parse::{Parse, ParseStream};
use syn::{
Attribute, Field, FieldsNamed, Ident, Token, Type, AttrStyle, Path,
Error, GenericParam, ItemStruct, Result, Visibility
};
use quote::{quote, ToTokens};
mod kw {
syn::custom_keyword!(wip);
syn::custom_keyword!(no_dispatch);
syn::custom_keyword!(blocking);
}
#[derive(Clone, Debug)]
enum SubSysAttrItem {
/// The subsystem is still a work in progress
/// and should not be communicated with.
Wip(kw::wip),
/// The subsystem is blocking and requires to be
/// spawned on an exclusive thread.
Blocking(kw::blocking),
/// External messages should not be - after being converted -
/// be dispatched to the annotated subsystem.
NoDispatch(kw::no_dispatch),
}
impl Parse for SubSysAttrItem {
fn parse(input: ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
Ok(if lookahead.peek(kw::wip) {
Self::Wip(input.parse::<kw::wip>()?)
} else if lookahead.peek(kw::blocking) {
Self::Blocking(input.parse::<kw::blocking>()?)
} else if lookahead.peek(kw::no_dispatch) {
Self::NoDispatch(input.parse::<kw::no_dispatch>()?)
} else {
return Err(lookahead.error())
})
}
}
impl ToTokens for SubSysAttrItem {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ts = match self {
Self::Wip(wip) => { quote!{ #wip } }
Self::Blocking(blocking) => { quote!{ #blocking } }
Self::NoDispatch(no_dispatch) => { quote!{ #no_dispatch } }
};
tokens.extend(ts.into_iter());
}
}
/// A field of the struct annotated with
/// `#[subsystem(no_dispatch, , A | B | C)]`
#[derive(Clone, Debug)]
pub(crate) struct SubSysField {
/// Name of the field.
pub(crate) name: Ident,
/// Generate generic type name for the `AllSubsystems` type
/// which is also used `#wrapper_message :: #variant` variant
/// part.
pub(crate) generic: Ident,
/// Type to be consumed by the subsystem.
pub(crate) consumes: Path,
/// If `no_dispatch` is present, if the message is incoming via
/// an extern `Event`, it will not be dispatched to all subsystems.
pub(crate) no_dispatch: bool,
/// If the subsystem implementation is blocking execution and hence
/// has to be spawned on a separate thread or thread pool.
pub(crate) blocking: bool,
/// The subsystem is a work in progress.
/// Avoids dispatching `Wrapper` type messages, but generates the variants.
/// Does not require the subsystem to be instanciated with the builder pattern.
pub(crate) wip: bool,
}
fn try_type_to_path(ty: Type, span: Span) -> Result<Path> {
match ty {
Type::Path(path) => Ok(path.path),
_ => Err(Error::new(span, "Type must be a path expression.")),
}
}
macro_rules! extract_variant {
($unique:expr, $variant:ident ; default = $fallback:expr) => {
extract_variant!($unique, $variant)
.unwrap_or_else(|| { $fallback })
};
($unique:expr, $variant:ident ; err = $err:expr) => {
extract_variant!($unique, $variant)
.ok_or_else(|| {
Error::new(Span::call_site(), $err)
})
};
($unique:expr, $variant:ident) => {
$unique.values()
.find_map(|item| {
if let SubSysAttrItem:: $variant ( _ ) = item {
Some(true)
} else {
None
}
})
};
}
pub(crate) struct SubSystemTags {
#[allow(dead_code)]
pub(crate) attrs: Vec<Attribute>,
#[allow(dead_code)]
pub(crate) no_dispatch: bool,
/// The subsystem is WIP, only generate the `Wrapper` variant, but do not forward messages
/// and also not include the subsystem in the list of subsystems.
pub(crate) wip: bool,
pub(crate) blocking: bool,
pub(crate) consumes: Path,
}
impl Parse for SubSystemTags {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let attrs = Attribute::parse_outer(input)?;
let input = input;
let content;
let _ = syn::parenthesized!(content in input);
let mut items = Punctuated::new();
while let Ok(tag) = content.call(SubSysAttrItem::parse) {
items.push_value(tag);
items.push_punct(content.call(<Token![,]>::parse)?);
}
assert!(items.empty_or_trailing(), "Always followed by the message type to consume. qed");
let consumes = content.parse::<Path>()?;
let mut unique = HashMap::<std::mem::Discriminant<SubSysAttrItem>, SubSysAttrItem, RandomState>::default();
for item in items {
if let Some(first) = unique.insert(std::mem::discriminant(&item), item.clone()) {
let mut e = Error::new(item.span(), format!("Duplicate definition of subsystem attribute found"));
e.combine(Error::new(first.span(), "previously defined here."));
return Err(e);
}
}
let no_dispatch = extract_variant!(unique, NoDispatch; default = false);
let blocking = extract_variant!(unique, Blocking; default = false);
let wip = extract_variant!(unique, Wip; default = false);
Ok(Self { attrs, no_dispatch, blocking, consumes, wip })
}
}
/// Fields that are _not_ subsystems.
#[derive(Debug, Clone)]
pub(crate) struct BaggageField {
pub(crate) field_name: Ident,
pub(crate) field_ty: Path,
pub(crate) generic: bool,
pub(crate) vis: Visibility,
}
#[derive(Clone, Debug)]
pub(crate) struct OverseerInfo {
/// Where the support crate `::polkadot_overseer_gen` lives.
pub(crate) support_crate_name: TokenStream,
/// Fields annotated with `#[subsystem(..)]`.
pub(crate) subsystems: Vec<SubSysField>,
/// Fields that do not define a subsystem,
/// but are mere baggage.
pub(crate) baggage: Vec<BaggageField>,
/// Name of the wrapping enum for all messages, defaults to `AllMessages`.
pub(crate) message_wrapper: Ident,
/// Name of the overseer struct, used as a prefix for
/// almost all generated types.
pub(crate) overseer_name: Ident,
/// Size of the bounded channel.
pub(crate) message_channel_capacity: usize,
/// Size of the bounded signal channel.
pub(crate) signal_channel_capacity: usize,
/// Signals to be sent, sparse information that is used intermittently.
pub(crate) extern_signal_ty: Path,
/// Incoming event type from the outer world, usually an external framework of some sort.
pub(crate) extern_event_ty: Path,
/// Incoming event type from an external entity, commonly from the network.
pub(crate) extern_network_ty: Option<Path>,
/// Type of messages that are sent to an external subsystem.
/// Merely here to be included during generation of `message_wrapper` type.
pub(crate) outgoing_ty: Option<Path>,
/// Incoming event type from the outer world, commonly from the network.
pub(crate) extern_error_ty: Path,
}
impl OverseerInfo {
pub(crate) fn support_crate_name(&self) -> &TokenStream {
&self.support_crate_name
}
pub(crate) fn variant_names(&self) -> Vec<Ident> {
self.subsystems
.iter()
.map(|ssf| ssf.generic.clone())
.collect::<Vec<_>>()
}
pub(crate) fn variant_names_without_wip(&self) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.generic.clone())
.collect::<Vec<_>>()
}
pub(crate) fn variant_names_only_wip(&self) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| ssf.wip)
.map(|ssf| ssf.generic.clone())
.collect::<Vec<_>>()
}
pub(crate) fn subsystems(&self) -> &[SubSysField] {
self.subsystems.as_slice()
}
pub(crate) fn subsystem_names_without_wip(&self) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.name.clone())
.collect::<Vec<_>>()
}
pub(crate) fn baggage_names(&self) -> Vec<Ident> {
self.baggage.iter().map(|bag| bag.field_name.clone()).collect::<Vec<_>>()
}
pub(crate) fn baggage_types(&self) -> Vec<Path> {
self.baggage.iter().map(|bag| bag.field_ty.clone()).collect::<Vec<_>>()
}
pub(crate) fn baggage_decl(&self) -> Vec<TokenStream> {
self.baggage
.iter()
.map(|bag| {
let BaggageField {
vis,
field_ty,
field_name,
..
} = bag;
quote!{ #vis #field_name: #field_ty }
})
.collect::<Vec<TokenStream>>()
}
/// Generic types per subsystem, as defined by the user.
pub(crate) fn builder_generic_types(&self) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|sff| sff.generic.clone())
.collect::<Vec<_>>()
}
pub(crate) fn baggage_generic_types(&self) -> Vec<Ident> {
self.baggage
.iter()
.filter(|bag| bag.generic)
.filter_map(|bag| bag.field_ty.get_ident().cloned())
.collect::<Vec<_>>()
}
pub(crate) fn consumes(&self) -> Vec<Path> {
self.subsystems.iter().map(|ssf| ssf.consumes.clone()).collect::<Vec<_>>()
}
pub(crate) fn channel_names_without_wip(&self, suffix: &'static str) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| Ident::new(&(ssf.name.to_string() + suffix), ssf.name.span()))
.collect::<Vec<_>>()
}
pub(crate) fn consumes_without_wip(&self) -> Vec<Path> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.consumes.clone())
.collect::<Vec<_>>()
}
}
/// Internals of the overseer.
#[derive(Debug, Clone)]
pub(crate) struct OverseerGuts {
pub(crate) name: Ident,
pub(crate) subsystems: Vec<SubSysField>,
pub(crate) baggage: Vec<BaggageField>,
}
impl OverseerGuts {
pub(crate) fn parse_fields(name: Ident, baggage_generics: HashSet<Ident>, fields: FieldsNamed) -> Result<Self> {
let n = fields.named.len();
let mut subsystems = Vec::with_capacity(n);
let mut baggage = Vec::with_capacity(n);
// The types of `#[subsystem(..)]` annotated fields
// have to be unique, since they are used as generics
// for the builder pattern besides other places.
let mut unique_subsystem_idents = HashSet::<Ident>::new();
for Field { attrs, vis, ident, ty, .. } in fields.named.into_iter() {
let mut consumes = attrs.iter().filter(|attr| attr.style == AttrStyle::Outer).filter_map(|attr| {
let span = attr.path.span();
attr.path.get_ident().filter(|ident| *ident == "subsystem").map(move |_ident| {
let attr_tokens = attr.tokens.clone();
(attr_tokens, span)
})
});
let ident = ident.ok_or_else(|| Error::new(ty.span(), "Missing identifier for member. BUG"))?;
if let Some((attr_tokens, span)) = consumes.next() {
if let Some((_attr_tokens2, span2)) = consumes.next() {
return Err({
let mut err = Error::new(span, "The first subsystem annotation is at");
err.combine(Error::new(span2, "but another here for the same field."));
err
});
}
let mut consumes_paths = Vec::with_capacity(attrs.len());
let attr_tokens = attr_tokens.clone();
let variant: SubSystemTags = syn::parse2(attr_tokens.clone())?;
consumes_paths.push(variant.consumes);
let field_ty = try_type_to_path(ty, span)?;
let generic = field_ty.get_ident().ok_or_else(|| Error::new(field_ty.span(), "Must be an identifier, not a path."))?.clone();
if let Some(previous) = unique_subsystem_idents.get(&generic) {
let mut e = Error::new(generic.span(), format!("Duplicate subsystem names `{}`", generic));
e.combine(Error::new(previous.span(), "previously defined here."));
return Err(e)
}
unique_subsystem_idents.insert(generic.clone());
subsystems.push(SubSysField {
name: ident,
generic,
consumes: consumes_paths[0].clone(),
no_dispatch: variant.no_dispatch,
wip: variant.wip,
blocking: variant.blocking,
});
} else {
let field_ty = try_type_to_path(ty, ident.span())?;
let generic = field_ty.get_ident().map(|ident| baggage_generics.contains(ident)).unwrap_or_default();
baggage.push(BaggageField { field_name: ident, generic, field_ty, vis });
}
}
Ok(Self { name, subsystems, baggage })
}
}
impl Parse for OverseerGuts {
fn parse(input: ParseStream) -> Result<Self> {
let ds: ItemStruct = input.parse()?;
match ds.fields {
syn::Fields::Named(named) => {
let name = ds.ident.clone();
// collect the indepedentent subsystem generics
// which need to be carried along, there are the non-generated ones
let mut orig_generics = ds.generics;
// remove defaults from types
let mut baggage_generic_idents = HashSet::with_capacity(orig_generics.params.len());
orig_generics.params = orig_generics
.params
.into_iter()
.map(|mut generic| {
match generic {
GenericParam::Type(ref mut param) => {
baggage_generic_idents.insert(param.ident.clone());
param.eq_token = None;
param.default = None;
}
_ => {}
}
generic
})
.collect();
Self::parse_fields(name, baggage_generic_idents, named)
}
syn::Fields::Unit => {
Err(Error::new(ds.fields.span(), "Must be a struct with named fields. Not an unit struct."))
}
syn::Fields::Unnamed(unnamed) => {
Err(Error::new(unnamed.span(), "Must be a struct with named fields. Not an unnamed fields struct."))
}
}
}
}
@@ -0,0 +1,117 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
use super::*;
use assert_matches::assert_matches;
use quote::quote;
use syn::parse_quote;
#[test]
fn print() {
let attr = quote! {
gen=AllMessage,
event=::some::why::ExternEvent,
signal=SigSigSig,
signal_capacity=111,
message_capacity=222,
error=OverseerError,
};
let item = quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(no_dispatch, Foo)]
sub0: FooSubsystem,
#[subsystem(blocking, Bar)]
yyy: BaersBuyBilliardBalls,
#[subsystem(no_dispatch, blocking, Twain)]
fff: Beeeeep,
#[subsystem(Rope)]
mc: MountainCave,
metrics: Metrics,
}
};
let output = impl_overseer_gen(attr, item).expect("Simple example always works. qed");
println!("//generated:");
println!("{}", output);
}
#[test]
fn struct_parse_full() {
let item: OverseerGuts = parse_quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(no_dispatch, Foo)]
sub0: FooSubsystem,
#[subsystem(blocking, Bar)]
yyy: BaersBuyBilliardBalls,
#[subsystem(no_dispatch, blocking, Twain)]
fff: Beeeeep,
#[subsystem(Rope)]
mc: MountainCave,
metrics: Metrics,
}
};
let _ = dbg!(item);
}
#[test]
fn struct_parse_basic() {
let item: OverseerGuts = parse_quote! {
pub struct Ooooh {
#[subsystem(Foo)]
sub0: FooSubsystem,
}
};
let _ = dbg!(item);
}
#[test]
fn attr_full() {
let attr: AttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=SigSigSig, signal_capacity=111, message_capacity=222,
error=OverseerError,
};
assert_matches!(attr, AttrArgs {
message_channel_capacity,
signal_channel_capacity,
..
} => {
assert_eq!(message_channel_capacity, 222);
assert_eq!(signal_channel_capacity, 111);
});
}
#[test]
fn attr_partial() {
let attr: AttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=::foo::SigSigSig,
error=OverseerError,
};
assert_matches!(attr, AttrArgs {
message_channel_capacity: _,
signal_channel_capacity: _,
..
} => {
});
}
@@ -0,0 +1,516 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Polkadot.
// Polkadot is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Polkadot is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
//! # Overseer
//!
//! `overseer` implements the Overseer architecture described in the
//! [implementers-guide](https://w3f.github.io/parachain-implementers-guide/node/index.html).
//! For the motivations behind implementing the overseer itself you should
//! check out that guide, documentation in this crate will be mostly discussing
//! technical stuff.
//!
//! An `Overseer` is something that allows spawning/stopping and overseeing
//! asynchronous tasks as well as establishing a well-defined and easy to use
//! protocol that the tasks can use to communicate with each other. It is desired
//! that this protocol is the only way tasks communicate with each other, however
//! at this moment there are no foolproof guards against other ways of communication.
//!
//! The `Overseer` is instantiated with a pre-defined set of `Subsystems` that
//! share the same behavior from `Overseer`'s point of view.
//!
//! ```text
//! +-----------------------------+
//! | Overseer |
//! +-----------------------------+
//!
//! ................| Overseer "holds" these and uses |..............
//! . them to (re)start things .
//! . .
//! . +-------------------+ +---------------------+ .
//! . | Subsystem1 | | Subsystem2 | .
//! . +-------------------+ +---------------------+ .
//! . | | .
//! ..................................................................
//! | |
//! start() start()
//! V V
//! ..................| Overseer "runs" these |.......................
//! . +--------------------+ +---------------------+ .
//! . | SubsystemInstance1 | <-- bidir --> | SubsystemInstance2 | .
//! . +--------------------+ +---------------------+ .
//! ..................................................................
//! ```
// #![deny(unused_results)]
// unused dependencies can not work for test and examples at the same time
// yielding false positives
#![deny(missing_docs)]
#![deny(unused_crate_dependencies)]
pub use polkadot_overseer_gen_proc_macro::overlord;
#[doc(hidden)]
pub use tracing;
#[doc(hidden)]
pub use metered;
#[doc(hidden)]
pub use sp_core::traits::SpawnNamed;
#[doc(hidden)]
pub use futures::{
self,
select,
StreamExt,
FutureExt,
poll,
future::{
Fuse, Future, BoxFuture
},
stream::{
self, select, FuturesUnordered,
},
task::{
Poll, Context,
},
channel::{mpsc, oneshot},
};
#[doc(hidden)]
pub use std::pin::Pin;
#[doc(hidden)]
pub use async_trait::async_trait;
#[doc(hidden)]
pub use std::time::Duration;
use std::sync::{Arc, atomic::{self, AtomicUsize}};
#[doc(hidden)]
pub use futures_timer::Delay;
pub use polkadot_node_network_protocol::WrongVariant;
use std::fmt;
#[cfg(test)]
mod tests;
/// A type of messages that are sent from [`Subsystem`] to [`Overseer`].
///
/// Used to launch jobs.
pub enum ToOverseer {
/// A message that wraps something the `Subsystem` is desiring to
/// spawn on the overseer and a `oneshot::Sender` to signal the result
/// of the spawn.
SpawnJob {
/// Name of the task to spawn which be shown in jaeger and tracing logs.
name: &'static str,
/// The future to execute.
s: BoxFuture<'static, ()>,
},
/// Same as `SpawnJob` but for blocking tasks to be executed on a
/// dedicated thread pool.
SpawnBlockingJob {
/// Name of the task to spawn which be shown in jaeger and tracing logs.
name: &'static str,
/// The future to execute.
s: BoxFuture<'static, ()>,
},
}
impl fmt::Debug for ToOverseer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SpawnJob{ name, .. } => writeln!(f, "SpawnJob{{ {}, ..}}", name),
Self::SpawnBlockingJob{ name, .. } => writeln!(f, "SpawnBlockingJob{{ {}, ..}}", name),
}
}
}
/// A helper trait to map a subsystem to smth. else.
pub trait MapSubsystem<T> {
/// The output type of the mapping.
type Output;
/// Consumes a `T` per subsystem, and maps it to `Self::Output`.
fn map_subsystem(&self, sub: T) -> Self::Output;
}
impl<F, T, U> MapSubsystem<T> for F where F: Fn(T) -> U {
type Output = U;
fn map_subsystem(&self, sub: T) -> U {
(self)(sub)
}
}
/// A wrapping type for messages.
///
/// Includes a counter to synchronize signals with messages,
/// such that no inconsistent message sequences are prevented.
#[derive(Debug)]
pub struct MessagePacket<T> {
/// Signal level at the point of reception.
///
/// Required to assure signals were consumed _before_
/// consuming messages that are based on the assumption
/// that a certain signal was assumed.
pub signals_received: usize,
/// The message to be sent/consumed.
pub message: T,
}
/// Create a packet from its parts.
pub fn make_packet<T>(signals_received: usize, message: T) -> MessagePacket<T> {
MessagePacket {
signals_received,
message,
}
}
/// Incoming messages from both the bounded and unbounded channel.
pub type SubsystemIncomingMessages<M> = self::stream::Select<
self::metered::MeteredReceiver<MessagePacket<M>>,
self::metered::UnboundedMeteredReceiver<MessagePacket<M>>,
>;
/// Watermark to track the received signals.
#[derive(Debug, Default, Clone)]
pub struct SignalsReceived(Arc<AtomicUsize>);
impl SignalsReceived {
/// Load the current value of received signals.
pub fn load(&self) -> usize {
// off by a few is ok
self.0.load(atomic::Ordering::Relaxed)
}
/// Increase the number of signals by one.
pub fn inc(&self) {
self.0.fetch_add(1, atomic::Ordering::Acquire);
}
}
/// A trait to support the origin annotation
/// such that errors across subsystems can be easier tracked.
pub trait AnnotateErrorOrigin: 'static + Send + Sync + std::error::Error {
/// Annotate the error with a origin `str`.
///
/// Commonly this is used to create nested enum variants.
///
/// ```rust,ignore
/// E::WithOrigin("I am originally from Cowtown.", E::Variant)
/// ```
fn with_origin(self, origin: &'static str) -> Self;
}
/// An asynchronous subsystem task..
///
/// In essence it's just a newtype wrapping a `BoxFuture`.
pub struct SpawnedSubsystem<E>
where
E: std::error::Error
+ Send
+ Sync
+ 'static
+ From<self::OverseerError>,
{
/// Name of the subsystem being spawned.
pub name: &'static str,
/// The task of the subsystem being spawned.
pub future: BoxFuture<'static, Result<(), E>>,
}
/// An error type that describes faults that may happen
///
/// These are:
/// * Channels being closed
/// * Subsystems dying when they are not expected to
/// * Subsystems not dying when they are told to die
/// * etc.
#[derive(thiserror::Error, Debug)]
#[allow(missing_docs)]
pub enum OverseerError {
#[error(transparent)]
NotifyCancellation(#[from] oneshot::Canceled),
#[error(transparent)]
QueueError(#[from] mpsc::SendError),
#[error("Failed to spawn task {0}")]
TaskSpawn(&'static str),
#[error(transparent)]
Infallible(#[from] std::convert::Infallible),
#[error("Failed to {0}")]
Context(String),
#[error("Subsystem stalled: {0}")]
SubsystemStalled(&'static str),
/// Per origin (or subsystem) annotations to wrap an error.
#[error("Error originated in {origin}")]
FromOrigin {
/// An additional annotation tag for the origin of `source`.
origin: &'static str,
/// The wrapped error. Marked as source for tracking the error chain.
#[source] source: Box<dyn 'static + std::error::Error + Send + Sync>
},
}
/// Alias for a result with error type `OverseerError`.
pub type OverseerResult<T> = std::result::Result<T, self::OverseerError>;
/// Collection of meters related to a subsystem.
#[derive(Clone)]
pub struct SubsystemMeters {
#[allow(missing_docs)]
pub bounded: metered::Meter,
#[allow(missing_docs)]
pub unbounded: metered::Meter,
#[allow(missing_docs)]
pub signals: metered::Meter,
}
impl SubsystemMeters {
/// Read the values of all subsystem `Meter`s.
pub fn read(&self) -> SubsystemMeterReadouts {
SubsystemMeterReadouts {
bounded: self.bounded.read(),
unbounded: self.unbounded.read(),
signals: self.signals.read(),
}
}
}
/// Set of readouts of the `Meter`s of a subsystem.
pub struct SubsystemMeterReadouts {
#[allow(missing_docs)]
pub bounded: metered::Readout,
#[allow(missing_docs)]
pub unbounded: metered::Readout,
#[allow(missing_docs)]
pub signals: metered::Readout,
}
/// A running instance of some [`Subsystem`].
///
/// [`Subsystem`]: trait.Subsystem.html
///
/// `M` here is the inner message type, and _not_ the generated `enum AllMessages`.
pub struct SubsystemInstance<Message, Signal> {
/// Send sink for `Signal`s to be sent to a subsystem.
pub tx_signal: crate::metered::MeteredSender<Signal>,
/// Send sink for `Message`s to be sent to a subsystem.
pub tx_bounded: crate::metered::MeteredSender<MessagePacket<Message>>,
/// All meters of the particular subsystem instance.
pub meters: SubsystemMeters,
/// The number of signals already received.
/// Required to assure messages and signals
/// are processed correctly.
pub signals_received: usize,
/// Name of the subsystem instance.
pub name: &'static str,
}
/// A message type that a subsystem receives from an overseer.
/// It wraps signals from an overseer and messages that are circulating
/// between subsystems.
///
/// It is generic over over the message type `M` that a particular `Subsystem` may use.
#[derive(Debug)]
pub enum FromOverseer<Message, Signal> {
/// Signal from the `Overseer`.
Signal(Signal),
/// Some other `Subsystem`'s message.
Communication {
/// Contained message
msg: Message,
},
}
impl<Signal, Message> From<Signal> for FromOverseer<Message, Signal> {
fn from(signal: Signal) -> Self {
Self::Signal(signal)
}
}
/// A context type that is given to the [`Subsystem`] upon spawning.
/// It can be used by [`Subsystem`] to communicate with other [`Subsystem`]s
/// or spawn jobs.
///
/// [`Overseer`]: struct.Overseer.html
/// [`SubsystemJob`]: trait.SubsystemJob.html
#[async_trait::async_trait]
pub trait SubsystemContext: Send + 'static {
/// The message type of this context. Subsystems launched with this context will expect
/// to receive messages of this type. Commonly uses the wrapping enum commonly called
/// `AllMessages`.
type Message: std::fmt::Debug + Send + 'static;
/// And the same for signals.
type Signal: std::fmt::Debug + Send + 'static;
/// The overarching all messages enum.
/// In some cases can be identical to `Self::Message`.
type AllMessages: From<Self::Message> + Send + 'static;
/// The sender type as provided by `sender()` and underlying.
type Sender: SubsystemSender<Self::AllMessages> + Send + 'static;
/// The error type.
type Error: ::std::error::Error + ::std::convert::From< OverseerError > + Sync + Send + 'static;
/// Try to asynchronously receive a message.
///
/// This has to be used with caution, if you loop over this without
/// using `pending!()` macro you will end up with a busy loop!
async fn try_recv(&mut self) -> Result<Option<FromOverseer<Self::Message, Self::Signal>>, ()>;
/// Receive a message.
async fn recv(&mut self) -> Result<FromOverseer<Self::Message, Self::Signal>, Self::Error>;
/// Spawn a child task on the executor.
fn spawn(
&mut self,
name: &'static str,
s: ::std::pin::Pin<Box<dyn crate::Future<Output = ()> + Send>>
) -> Result<(), Self::Error>;
/// Spawn a blocking child task on the executor's dedicated thread pool.
fn spawn_blocking(
&mut self,
name: &'static str,
s: ::std::pin::Pin<Box<dyn crate::Future<Output = ()> + Send>>,
) -> Result<(), Self::Error>;
/// Send a direct message to some other `Subsystem`, routed based on message type.
async fn send_message<X>(&mut self, msg: X)
where
Self::AllMessages: From<X>,
X: Send,
{
self.sender().send_message(<Self::AllMessages>::from(msg)).await
}
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
async fn send_messages<X, T>(&mut self, msgs: T)
where
T: IntoIterator<Item = X> + Send,
T::IntoIter: Send,
Self::AllMessages: From<X>,
X: Send,
{
self.sender().send_messages(msgs.into_iter().map(|x| <Self::AllMessages>::from(x))).await
}
/// Send a message using the unbounded connection.
fn send_unbounded_message<X>(&mut self, msg: X)
where
Self::AllMessages: From<X>,
X: Send,
{
self.sender().send_unbounded_message(Self::AllMessages::from(msg))
}
/// Obtain the sender.
fn sender(&mut self) -> &mut Self::Sender;
}
/// A trait that describes the [`Subsystem`]s that can run on the [`Overseer`].
///
/// It is generic over the message type circulating in the system.
/// The idea that we want some type containing persistent state that
/// can spawn actually running subsystems when asked.
///
/// [`Overseer`]: struct.Overseer.html
/// [`Subsystem`]: trait.Subsystem.html
pub trait Subsystem<Ctx, E>
where
Ctx: SubsystemContext,
E: std::error::Error + Send + Sync + 'static + From<self::OverseerError>,
{
/// Start this `Subsystem` and return `SpawnedSubsystem`.
fn start(self, ctx: Ctx) -> SpawnedSubsystem < E >;
}
/// Sender end of a channel to interface with a subsystem.
#[async_trait::async_trait]
pub trait SubsystemSender<Message>: Send + Clone + 'static {
/// Send a direct message to some other `Subsystem`, routed based on message type.
async fn send_message(&mut self, msg: Message);
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
async fn send_messages<T>(&mut self, msgs: T)
where T: IntoIterator<Item = Message> + Send, T::IntoIter: Send;
/// Send a message onto the unbounded queue of some other `Subsystem`, routed based on message
/// type.
///
/// This function should be used only when there is some other bounding factor on the messages
/// sent with it. Otherwise, it risks a memory leak.
fn send_unbounded_message(&mut self, msg: Message);
}
/// A future that wraps another future with a `Delay` allowing for time-limited futures.
#[pin_project::pin_project]
pub struct Timeout<F: Future> {
#[pin]
future: F,
#[pin]
delay: Delay,
}
/// Extends `Future` to allow time-limited futures.
pub trait TimeoutExt: Future {
/// Adds a timeout of `duration` to the given `Future`.
/// Returns a new `Future`.
fn timeout(self, duration: Duration) -> Timeout<Self>
where
Self: Sized,
{
Timeout {
future: self,
delay: Delay::new(duration),
}
}
}
impl<F> TimeoutExt for F where F: Future{}
impl<F> Future for Timeout<F> where F: Future {
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, ctx: &mut Context) -> Poll<Self::Output> {
let this = self.project();
if this.delay.poll(ctx).is_ready() {
return Poll::Ready(None);
}
if let Poll::Ready(output) = this.future.poll(ctx) {
return Poll::Ready(Some(output));
}
Poll::Pending
}
}
@@ -0,0 +1,10 @@
// The generated code requires quite a bit of surrounding code to work.
// Please refer to [the examples](examples/dummy.rs) and
// [the minimal usage example](../examples/minimal-example.rs).
#[test]
fn ui_compile_fail() {
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/err-*.rs");
}
@@ -0,0 +1,38 @@
#![allow(dead_code)]
use polkadot_overseer_gen::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Default)]
struct AwesomeSubSys2;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone)]
struct MsgStrukt(u8);
#[overlord(signal=SigSigSig, event=Event, gen=AllMessages, error=OverseerError)]
struct Overseer {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
#[subsystem(MsgStrukt)]
sub1: AwesomeSubSys2,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let overseer = Overseer::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -0,0 +1,21 @@
error[E0119]: conflicting implementations of trait `std::convert::From<MsgStrukt>` for type `AllMessages`
--> $DIR/err-01-duplicate-consumer.rs:19:1
|
19 | #[overlord(signal=SigSigSig, event=Event, gen=AllMessages, error=OverseerError)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `AllMessages`
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `polkadot_overseer_gen::SubsystemSender<MsgStrukt>` for type `OverseerSubsystemSender`
--> $DIR/err-01-duplicate-consumer.rs:19:1
|
19 | #[overlord(signal=SigSigSig, event=Event, gen=AllMessages, error=OverseerError)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `OverseerSubsystemSender`
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -0,0 +1,32 @@
#![allow(dead_code)]
use polkadot_overseer_gen::*;
#[derive(Default)]
struct AwesomeSubSys;
struct SigSigSig;
struct Event;
#[derive(Clone, Debug)]
struct MsgStrukt(u8);
#[overlord(signal=SigSigSig, event=Event, gen=AllMessages, error=OverseerError)]
enum Overseer {
#[subsystem(MsgStrukt)]
Sub0(AwesomeSubSys),
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let overseer = Overseer::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -0,0 +1,11 @@
error: expected `struct`
--> $DIR/err-02-enum.rs:16:1
|
16 | enum Overseer {
| ^^^^
error[E0433]: failed to resolve: use of undeclared type `Overseer`
--> $DIR/err-02-enum.rs:27:17
|
27 | let overseer = Overseer::<_,_>::builder()
| ^^^^^^^^ use of undeclared type `Overseer`
@@ -0,0 +1,39 @@
#![allow(dead_code)]
use polkadot_overseer_gen::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone, Debug)]
struct MsgStrukt(u8);
#[derive(Clone, Debug)]
struct MsgStrukt2(f64);
#[overlord(signal=SigSigSig, event=Event, gen=AllMessages, error=OverseerError)]
struct Overseer {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
#[subsystem(MsgStrukt2)]
sub1: AwesomeSubSys,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let overseer = Overseer::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -0,0 +1,17 @@
error: Duplicate subsystem names `AwesomeSubSys`
--> $DIR/err-03-subsys-twice.rs:25:8
|
25 | sub1: AwesomeSubSys,
| ^^^^^^^^^^^^^
error: previously defined here.
--> $DIR/err-03-subsys-twice.rs:22:8
|
22 | sub0: AwesomeSubSys,
| ^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type `Overseer`
--> $DIR/err-03-subsys-twice.rs:34:17
|
34 | let overseer = Overseer::<_,_>::builder()
| ^^^^^^^^ use of undeclared type `Overseer`
@@ -0,0 +1,36 @@
#![allow(dead_code)]
use polkadot_overseer_gen::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone)]
struct MsgStrukt(u8);
#[overlord(signal=SigSigSig, event=Event, gen=AllMessages)]
struct Overseer {
#[subsystem(no_dispatch, MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let _ = Overseer::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -0,0 +1,13 @@
error: Must declare the overseer error type via `error=..`.
--> $DIR/err-04-missing-error.rs:16:1
|
16 | #[overlord(signal=SigSigSig, event=Event, gen=AllMessages)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0433]: failed to resolve: use of undeclared type `Overseer`
--> $DIR/err-04-missing-error.rs:30:10
|
30 | let _ = Overseer::builder()
| ^^^^^^^^ use of undeclared type `Overseer`