Remove orchestra and metered channel (#6086)

* Use orchestra and metered channel from repo

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

* Update node/subsystem-util/Cargo.toml

Co-authored-by: Andronik <write@reusable.software>

* Update node/subsystem-types/Cargo.toml

Co-authored-by: Andronik <write@reusable.software>

* Update node/metrics/Cargo.toml

Co-authored-by: Andronik <write@reusable.software>

* Update node/overseer/Cargo.toml

Co-authored-by: Andronik <write@reusable.software>

* Update node/overseer/Cargo.toml

Co-authored-by: Andronik <write@reusable.software>

* update cargo lock

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>

Signed-off-by: Andrei Sandu <andrei-mihail@parity.io>
Co-authored-by: Andronik <write@reusable.software>
This commit is contained in:
Andrei Sandu
2022-10-03 13:26:23 +03:00
committed by GitHub
parent 5458406927
commit d7eaaeee65
56 changed files with 14 additions and 7014 deletions
+8 -12
View File
@@ -4758,7 +4758,9 @@ dependencies = [
[[package]]
name = "orchestra"
version = "0.0.1"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aab54694ddaa8a9b703724c6ef04272b2d27bc32d2c855aae5cdd1857216b43"
dependencies = [
"async-trait",
"dyn-clonable",
@@ -4767,27 +4769,23 @@ dependencies = [
"orchestra-proc-macro",
"pin-project",
"prioritized-metered-channel",
"rustversion",
"thiserror",
"tracing",
"trybuild",
]
[[package]]
name = "orchestra-proc-macro"
version = "0.0.1"
version = "0.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a702b2f6bf592b3eb06c00d80d05afaf7a8eff6b41bb361e397d799acc21b45a"
dependencies = [
"assert_matches",
"expander 0.0.6",
"itertools",
"orchestra",
"petgraph",
"proc-macro-crate",
"proc-macro2",
"quote",
"syn",
"thiserror",
"tracing",
]
[[package]]
@@ -7668,15 +7666,14 @@ dependencies = [
[[package]]
name = "prioritized-metered-channel"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "382698e48a268c832d0b181ed438374a6bb708a82a8ca273bb0f61c74cf209c4"
dependencies = [
"assert_matches",
"coarsetime",
"crossbeam-queue",
"derive_more",
"env_logger 0.9.0",
"futures",
"futures-timer",
"log",
"nanorand",
"thiserror",
"tracing",
@@ -11587,7 +11584,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
dependencies = [
"cfg-if 1.0.0",
"log",
"pin-project-lite 0.2.7",
"tracing-attributes",
"tracing-core",
-3
View File
@@ -83,8 +83,6 @@ members = [
"node/network/gossip-support",
"node/network/dispute-distribution",
"node/overseer",
"node/orchestra",
"node/orchestra/proc-macro",
"node/malus",
"node/primitives",
"node/service",
@@ -96,7 +94,6 @@ members = [
"node/gum",
"node/gum/proc-macro",
"node/metrics",
"node/metered-channel",
"node/test/client",
"node/test/performance-test",
"node/test/service",
-25
View File
@@ -1,25 +0,0 @@
[package]
name = "prioritized-metered-channel"
version = "0.2.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
description = "Channels with built-in observability and message priorizitazion (coming soon™)"
repository = "https://github.com/paritytech/polkadot.git"
license = "MIT OR Apache-2.0"
[dependencies]
futures = "0.3.21"
futures-timer = "3.0.2"
derive_more = "0.99"
tracing = "0.1.35"
thiserror = "1.0.31"
crossbeam-queue = "0.3.5"
nanorand = { version = "0.7.0", default-features = false, features = ["wyrand"] }
coarsetime = "^0.1.22"
[dev-dependencies]
futures = { version = "0.3.21", features = ["thread-pool"] }
assert_matches = "1.5"
env_logger = "0.9"
log = "0.4"
tracing = { version = "0.1.35", features = ["log"] }
@@ -1,195 +0,0 @@
// Copyright 2017-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/>.
//! Metered variant of bounded mpsc channels to be able to extract metrics.
use futures::{
channel::mpsc,
sink::SinkExt,
stream::Stream,
task::{Context, Poll},
};
use std::{pin::Pin, result};
use super::{measure_tof_check, CoarseInstant, MaybeTimeOfFlight, Meter};
/// Create a wrapped `mpsc::channel` pair of `MeteredSender` and `MeteredReceiver`.
pub fn channel<T>(capacity: usize) -> (MeteredSender<T>, MeteredReceiver<T>) {
let (tx, rx) = mpsc::channel::<MaybeTimeOfFlight<T>>(capacity);
let shared_meter = Meter::default();
let tx = MeteredSender { meter: shared_meter.clone(), inner: tx };
let rx = MeteredReceiver { meter: shared_meter, inner: rx };
(tx, rx)
}
/// A receiver tracking the messages consumed by itself.
#[derive(Debug)]
pub struct MeteredReceiver<T> {
// count currently contained messages
meter: Meter,
inner: mpsc::Receiver<MaybeTimeOfFlight<T>>,
}
impl<T> std::ops::Deref for MeteredReceiver<T> {
type Target = mpsc::Receiver<MaybeTimeOfFlight<T>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for MeteredReceiver<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> Stream for MeteredReceiver<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match mpsc::Receiver::poll_next(Pin::new(&mut self.inner), cx) {
Poll::Ready(maybe_value) => Poll::Ready(self.maybe_meter_tof(maybe_value)),
Poll::Pending => Poll::Pending,
}
}
/// Don't rely on the unreliable size hint.
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<T> MeteredReceiver<T> {
fn maybe_meter_tof(&mut self, maybe_value: Option<MaybeTimeOfFlight<T>>) -> Option<T> {
self.meter.note_received();
maybe_value.map(|value| {
match value {
MaybeTimeOfFlight::<T>::WithTimeOfFlight(value, tof_start) => {
// do not use `.elapsed()` of `std::time`, it may panic
// `coarsetime` does a saturating sub for all `CoarseInstant` substractions
let duration = tof_start.elapsed();
self.meter.note_time_of_flight(duration);
value
},
MaybeTimeOfFlight::<T>::Bare(value) => value,
}
.into()
})
}
/// Get an updated accessor object for all metrics collected.
pub fn meter(&self) -> &Meter {
&self.meter
}
/// Attempt to receive the next item.
pub fn try_next(&mut self) -> Result<Option<T>, mpsc::TryRecvError> {
match self.inner.try_next()? {
Some(value) => Ok(self.maybe_meter_tof(Some(value))),
None => Ok(None),
}
}
}
impl<T> futures::stream::FusedStream for MeteredReceiver<T> {
fn is_terminated(&self) -> bool {
self.inner.is_terminated()
}
}
/// The sender component, tracking the number of items
/// sent across it.
#[derive(Debug)]
pub struct MeteredSender<T> {
meter: Meter,
inner: mpsc::Sender<MaybeTimeOfFlight<T>>,
}
impl<T> Clone for MeteredSender<T> {
fn clone(&self) -> Self {
Self { meter: self.meter.clone(), inner: self.inner.clone() }
}
}
impl<T> std::ops::Deref for MeteredSender<T> {
type Target = mpsc::Sender<MaybeTimeOfFlight<T>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for MeteredSender<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> MeteredSender<T> {
fn prepare_with_tof(&self, item: T) -> MaybeTimeOfFlight<T> {
let previous = self.meter.note_sent();
let item = if measure_tof_check(previous) {
MaybeTimeOfFlight::WithTimeOfFlight(item, CoarseInstant::now())
} else {
MaybeTimeOfFlight::Bare(item)
};
item
}
/// Get an updated accessor object for all metrics collected.
pub fn meter(&self) -> &Meter {
&self.meter
}
/// Send message, wait until capacity is available.
pub async fn send(&mut self, msg: T) -> result::Result<(), mpsc::SendError>
where
Self: Unpin,
{
match self.try_send(msg) {
Err(send_err) => {
if !send_err.is_full() {
return Err(send_err.into_send_error())
}
let msg = send_err.into_inner();
self.meter.note_sent();
let fut = self.inner.send(msg);
futures::pin_mut!(fut);
fut.await.map_err(|e| {
self.meter.retract_sent();
e
})
},
_ => Ok(()),
}
}
/// Attempt to send message or fail immediately.
pub fn try_send(
&mut self,
msg: T,
) -> result::Result<(), mpsc::TrySendError<MaybeTimeOfFlight<T>>> {
let msg = self.prepare_with_tof(msg);
self.inner.try_send(msg).map_err(|e| {
if e.is_full() {
// Count bounded channel sends that block.
self.meter.note_blocked();
}
self.meter.retract_sent();
e
})
}
}
-166
View File
@@ -1,166 +0,0 @@
// Copyright 2017-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/>.
//! Metered variant of mpsc channels to be able to extract metrics.
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
};
use derive_more::Display;
mod bounded;
pub mod oneshot;
mod unbounded;
pub use self::{bounded::*, unbounded::*};
pub use coarsetime::Duration as CoarseDuration;
use coarsetime::Instant as CoarseInstant;
#[cfg(test)]
mod tests;
/// A peek into the inner state of a meter.
#[derive(Debug, Clone)]
pub struct Meter {
// Number of sends on this channel.
sent: Arc<AtomicUsize>,
// Number of receives on this channel.
received: Arc<AtomicUsize>,
// Number of times senders blocked while sending messages to a subsystem.
blocked: Arc<AtomicUsize>,
// Atomic ringbuffer of the last 50 time of flight values
tof: Arc<crossbeam_queue::ArrayQueue<CoarseDuration>>,
}
impl std::default::Default for Meter {
fn default() -> Self {
Self {
sent: Arc::new(AtomicUsize::new(0)),
received: Arc::new(AtomicUsize::new(0)),
blocked: Arc::new(AtomicUsize::new(0)),
tof: Arc::new(crossbeam_queue::ArrayQueue::new(100)),
}
}
}
/// A readout of sizes from the meter. Note that it is possible, due to asynchrony, for received
/// to be slightly higher than sent.
#[derive(Debug, Display, Clone, Default, PartialEq)]
#[display(fmt = "(sent={} received={})", sent, received)]
pub struct Readout {
/// The amount of messages sent on the channel, in aggregate.
pub sent: usize,
/// The amount of messages received on the channel, in aggregate.
pub received: usize,
/// How many times the caller blocked when sending messages.
pub blocked: usize,
/// Time of flight in micro seconds (us)
pub tof: Vec<CoarseDuration>,
}
impl Meter {
/// Count the number of items queued up inside the channel.
pub fn read(&self) -> Readout {
// when obtaining we don't care much about off by one
// accuracy
Readout {
sent: self.sent.load(Ordering::Relaxed),
received: self.received.load(Ordering::Relaxed),
blocked: self.blocked.load(Ordering::Relaxed),
tof: {
let mut acc = Vec::with_capacity(self.tof.len());
while let Some(value) = self.tof.pop() {
acc.push(value)
}
acc
},
}
}
fn note_sent(&self) -> usize {
self.sent.fetch_add(1, Ordering::Relaxed)
}
fn retract_sent(&self) {
self.sent.fetch_sub(1, Ordering::Relaxed);
}
fn note_received(&self) {
self.received.fetch_add(1, Ordering::Relaxed);
}
fn note_blocked(&self) {
self.blocked.fetch_add(1, Ordering::Relaxed);
}
fn note_time_of_flight(&self, tof: CoarseDuration) {
let _ = self.tof.force_push(tof);
}
}
/// Determine if this instance shall be measured
#[inline(always)]
fn measure_tof_check(nth: usize) -> bool {
if cfg!(test) {
// for tests, be deterministic and pick every second
nth & 0x01 == 0
} else {
use nanorand::Rng;
let mut rng = nanorand::WyRand::new_seed(nth as u64);
let pick = rng.generate_range(1_usize..=1000);
// measure 5.3%
pick <= 53
}
}
/// Measure the time of flight between insertion and removal
/// of a single type `T`
#[derive(Debug)]
pub enum MaybeTimeOfFlight<T> {
Bare(T),
WithTimeOfFlight(T, CoarseInstant),
}
impl<T> From<T> for MaybeTimeOfFlight<T> {
fn from(value: T) -> Self {
Self::Bare(value)
}
}
// Has some unexplicable conflict with a wildcard impl of std
impl<T> MaybeTimeOfFlight<T> {
/// Extract the inner `T` value.
pub fn into(self) -> T {
match self {
Self::Bare(value) => value,
Self::WithTimeOfFlight(value, _tof_start) => value,
}
}
}
impl<T> std::ops::Deref for MaybeTimeOfFlight<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
match self {
Self::Bare(ref value) => value,
Self::WithTimeOfFlight(ref value, _tof_start) => value,
}
}
}
@@ -1,419 +0,0 @@
// 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/>.
//! Metered variant of oneshot channels to be able to extract delays caused by delayed responses.
use std::{
ops::Deref,
pin::Pin,
task::{Context, Poll},
};
use futures::{
channel::oneshot::{self, Canceled, Cancellation},
future::{Fuse, FusedFuture},
prelude::*,
};
use futures_timer::Delay;
use crate::{CoarseDuration, CoarseInstant};
/// Provides the reason for termination.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Reason {
Completion = 1,
Cancellation = 2,
HardTimeout = 3,
}
/// Obtained measurements by the `Receiver` side of the `MeteredOneshot`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Measurements {
/// Duration between first poll and polling termination.
first_poll_till_end: CoarseDuration,
/// Duration starting with creation until polling termination.
creation_till_end: CoarseDuration,
/// Reason for resolving the future.
reason: Reason,
}
impl Measurements {
/// Obtain the duration of a finished or canceled
/// `oneshot` channel.
pub fn duration_since_first_poll(&self) -> &CoarseDuration {
&self.first_poll_till_end
}
/// Obtain the duration of a finished or canceled
/// `oneshot` channel.
pub fn duration_since_creation(&self) -> &CoarseDuration {
&self.creation_till_end
}
/// Obtain the reason to the channel termination.
pub fn reason(&self) -> &Reason {
&self.reason
}
}
/// Create a new pair of `OneshotMetered{Sender,Receiver}`.
pub fn channel<T>(
name: &'static str,
soft_timeout: CoarseDuration,
hard_timeout: CoarseDuration,
) -> (MeteredSender<T>, MeteredReceiver<T>) {
let (tx, rx) = oneshot::channel();
(
MeteredSender { inner: tx },
MeteredReceiver {
name,
inner: rx,
soft_timeout,
hard_timeout,
soft_timeout_fut: None,
hard_timeout_fut: None,
first_poll_timestamp: None,
creation_timestamp: CoarseInstant::now(),
},
)
}
#[allow(missing_docs)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Oneshot was canceled.")]
Canceled(#[source] Canceled, Measurements),
#[error("Oneshot did not receive a response within {}", CoarseDuration::as_f64(.0))]
HardTimeout(CoarseDuration, Measurements),
}
impl Measurable for Error {
fn measurements(&self) -> Measurements {
match self {
Self::Canceled(_, measurements) => measurements.clone(),
Self::HardTimeout(_, measurements) => measurements.clone(),
}
}
}
/// Oneshot sender, created by [`channel`].
#[derive(Debug)]
pub struct MeteredSender<T> {
inner: oneshot::Sender<(CoarseInstant, T)>,
}
impl<T> MeteredSender<T> {
/// Send a value.
pub fn send(self, t: T) -> Result<(), T> {
let Self { inner } = self;
inner.send((CoarseInstant::now(), t)).map_err(|(_, t)| t)
}
/// Poll if the thing is already canceled.
pub fn poll_canceled(&mut self, ctx: &mut Context<'_>) -> Poll<()> {
self.inner.poll_canceled(ctx)
}
/// Access the cancellation object.
pub fn cancellation(&mut self) -> Cancellation<'_, (CoarseInstant, T)> {
self.inner.cancellation()
}
/// Check the cancellation state.
pub fn is_canceled(&self) -> bool {
self.inner.is_canceled()
}
/// Verify if the `receiver` is connected to the `sender` [`Self`].
pub fn is_connected_to(&self, receiver: &MeteredReceiver<T>) -> bool {
self.inner.is_connected_to(&receiver.inner)
}
}
/// Oneshot receiver, created by [`channel`].
#[derive(Debug)]
pub struct MeteredReceiver<T> {
name: &'static str,
inner: oneshot::Receiver<(CoarseInstant, T)>,
/// Soft timeout, on expire a warning is printed.
soft_timeout_fut: Option<Fuse<Delay>>,
soft_timeout: CoarseDuration,
/// Hard timeout, terminating the sender.
hard_timeout_fut: Option<Delay>,
hard_timeout: CoarseDuration,
/// The first time the receiver was polled.
first_poll_timestamp: Option<CoarseInstant>,
creation_timestamp: CoarseInstant,
}
impl<T> MeteredReceiver<T> {
pub fn close(&mut self) {
self.inner.close()
}
/// Attempts to receive a message outside of the context of a task.
///
/// A return value of `None` must be considered immediately stale (out of
/// date) unless [`close`](MeteredReceiver::close) has been called first.
///
/// Returns an error if the sender was dropped.
pub fn try_recv(&mut self) -> Result<Option<OutputWithMeasurements<T>>, Error> {
match self.inner.try_recv() {
Ok(Some((when, value))) => {
let measurements = self.create_measurement(when, Reason::Completion);
Ok(Some(OutputWithMeasurements { value, measurements }))
},
Err(e) => {
let measurements = self.create_measurement(
self.first_poll_timestamp.unwrap_or_else(|| CoarseInstant::now()),
Reason::Cancellation,
);
Err(Error::Canceled(e, measurements))
},
Ok(None) => Ok(None),
}
}
/// Helper to create a measurement.
///
/// `start` determines the first possible time where poll can resolve with `Ready`.
fn create_measurement(&self, start: CoarseInstant, reason: Reason) -> Measurements {
let end = CoarseInstant::now();
Measurements {
// negative values are ok, if `send` was called before we poll for the first time.
first_poll_till_end: end - start,
creation_till_end: end - self.creation_timestamp,
reason,
}
}
}
impl<T> FusedFuture for MeteredReceiver<T> {
fn is_terminated(&self) -> bool {
self.inner.is_terminated()
}
}
impl<T> Future for MeteredReceiver<T> {
type Output = Result<OutputWithMeasurements<T>, Error>;
fn poll(
mut self: Pin<&mut Self>,
ctx: &mut Context<'_>,
) -> Poll<Result<OutputWithMeasurements<T>, Error>> {
let first_poll_timestamp =
self.first_poll_timestamp.get_or_insert_with(|| CoarseInstant::now()).clone();
let soft_timeout = self.soft_timeout.clone().into();
let soft_timeout = self
.soft_timeout_fut
.get_or_insert_with(move || Delay::new(soft_timeout).fuse());
if Pin::new(soft_timeout).poll(ctx).is_ready() {
tracing::warn!(target: "oneshot", "Oneshot `{name}` exceeded the soft threshold", name = &self.name);
}
let hard_timeout = self.hard_timeout.clone().into();
let hard_timeout =
self.hard_timeout_fut.get_or_insert_with(move || Delay::new(hard_timeout));
if Pin::new(hard_timeout).poll(ctx).is_ready() {
let measurements = self.create_measurement(first_poll_timestamp, Reason::HardTimeout);
return Poll::Ready(Err(Error::HardTimeout(self.hard_timeout.clone(), measurements)))
}
match Pin::new(&mut self.inner).poll(ctx) {
Poll::Pending => Poll::Pending,
Poll::Ready(Err(e)) => {
let measurements =
self.create_measurement(first_poll_timestamp, Reason::Cancellation);
Poll::Ready(Err(Error::Canceled(e, measurements)))
},
Poll::Ready(Ok((ref sent_at_timestamp, value))) => {
let measurements =
self.create_measurement(sent_at_timestamp.clone(), Reason::Completion);
Poll::Ready(Ok(OutputWithMeasurements::<T> { value, measurements }))
},
}
}
}
/// A dummy trait that allows implementing `measurements` for `Result<_,_>`.
pub trait Measurable {
/// Obtain a set of measurements represented by the `Measurements` type.
fn measurements(&self) -> Measurements;
}
impl<T> Measurable for Result<OutputWithMeasurements<T>, Error> {
fn measurements(&self) -> Measurements {
match self {
Err(err) => err.measurements(),
Ok(val) => val.measurements(),
}
}
}
/// A wrapping type for the actual type `T` that is sent with the
/// oneshot yet allow to attach `Measurements` to it.
///
/// Implements `AsRef` besides others for easier access to the inner,
/// wrapped type.
#[derive(Clone, Debug)]
pub struct OutputWithMeasurements<T> {
value: T,
measurements: Measurements,
}
impl<T> Measurable for OutputWithMeasurements<T> {
fn measurements(&self) -> Measurements {
self.measurements.clone()
}
}
impl<T> OutputWithMeasurements<T> {
/// Converts the wrapper type into it's inner value.
///
/// `trait Into` cannot be implemented due to conflicts.
pub fn into(self) -> T {
self.value
}
}
impl<T> AsRef<T> for OutputWithMeasurements<T> {
fn as_ref(&self) -> &T {
&self.value
}
}
impl<T> Deref for OutputWithMeasurements<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.value
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use futures::{executor::ThreadPool, task::SpawnExt};
use std::time::Duration;
use super::*;
#[derive(Clone, PartialEq, Eq, Debug)]
struct DummyItem {
vals: [u8; 256],
}
impl Default for DummyItem {
fn default() -> Self {
Self { vals: [0u8; 256] }
}
}
fn test_launch<S, R, FS, FR>(name: &'static str, gen_sender_test: S, gen_receiver_test: R)
where
S: Fn(MeteredSender<DummyItem>) -> FS,
R: Fn(MeteredReceiver<DummyItem>) -> FR,
FS: Future<Output = ()> + Send + 'static,
FR: Future<Output = ()> + Send + 'static,
{
let _ = env_logger::builder().is_test(true).filter_level(LevelFilter::Trace).try_init();
let pool = ThreadPool::new().unwrap();
let (tx, rx) = channel(name, CoarseDuration::from_secs(1), CoarseDuration::from_secs(3));
futures::executor::block_on(async move {
let handle_receiver = pool.spawn_with_handle(gen_receiver_test(rx)).unwrap();
let handle_sender = pool.spawn_with_handle(gen_sender_test(tx)).unwrap();
futures::future::select(
futures::future::join(handle_sender, handle_receiver),
Delay::new(Duration::from_secs(5)),
)
.await;
});
}
use log::LevelFilter;
#[test]
fn easy() {
test_launch(
"easy",
|tx| async move {
tx.send(DummyItem::default()).unwrap();
},
|rx| async move {
let x = rx.await.unwrap();
let measurements = x.measurements();
assert_eq!(x.as_ref(), &DummyItem::default());
dbg!(measurements);
},
);
}
#[test]
fn cancel_by_drop() {
test_launch(
"cancel_by_drop",
|tx| async move {
Delay::new(Duration::from_secs(2)).await;
drop(tx);
},
|rx| async move {
let result = rx.await;
assert_matches!(result, Err(Error::Canceled(_, _)));
dbg!(result.measurements());
},
);
}
#[test]
fn starve_till_hard_timeout() {
test_launch(
"starve_till_timeout",
|tx| async move {
Delay::new(Duration::from_secs(4)).await;
let _ = tx.send(DummyItem::default());
},
|rx| async move {
let result = rx.await;
assert_matches!(&result, e @ &Err(Error::HardTimeout(_, _)) => {
println!("{:?}", e);
});
dbg!(result.measurements());
},
);
}
#[test]
fn starve_till_soft_timeout_then_food() {
test_launch(
"starve_till_soft_timeout_then_food",
|tx| async move {
Delay::new(Duration::from_secs(2)).await;
let _ = tx.send(DummyItem::default());
},
|rx| async move {
let result = rx.await;
assert_matches!(result, Ok(_));
dbg!(result.measurements());
},
);
}
}
-150
View File
@@ -1,150 +0,0 @@
// 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 futures::{executor::block_on, StreamExt};
#[derive(Clone, Copy, Debug, Default)]
struct Msg {
val: u8,
}
#[test]
fn try_send_try_next() {
block_on(async move {
let (mut tx, mut rx) = channel::<Msg>(5);
let msg = Msg::default();
assert_matches!(rx.meter().read(), Readout { sent: 0, received: 0, .. });
tx.try_send(msg).unwrap();
assert_matches!(tx.meter().read(), Readout { sent: 1, received: 0, .. });
tx.try_send(msg).unwrap();
tx.try_send(msg).unwrap();
tx.try_send(msg).unwrap();
assert_matches!(tx.meter().read(), Readout { sent: 4, received: 0, .. });
rx.try_next().unwrap();
assert_matches!(rx.meter().read(), Readout { sent: 4, received: 1, .. });
rx.try_next().unwrap();
rx.try_next().unwrap();
assert_matches!(tx.meter().read(), Readout { sent: 4, received: 3, blocked: 0, tof } => {
// every second in test, consumed before
assert_eq!(dbg!(tof).len(), 1);
});
rx.try_next().unwrap();
assert_matches!(rx.meter().read(), Readout { sent: 4, received: 4, blocked: 0, tof } => {
// every second in test, consumed before
assert_eq!(dbg!(tof).len(), 0);
});
assert!(rx.try_next().is_err());
});
}
#[test]
fn with_tasks() {
let (ready, go) = futures::channel::oneshot::channel();
let (mut tx, mut rx) = channel::<Msg>(5);
block_on(async move {
futures::join!(
async move {
let msg = Msg::default();
assert_matches!(tx.meter().read(), Readout { sent: 0, received: 0, .. });
tx.try_send(msg).unwrap();
assert_matches!(tx.meter().read(), Readout { sent: 1, received: 0, .. });
tx.try_send(msg).unwrap();
tx.try_send(msg).unwrap();
tx.try_send(msg).unwrap();
ready.send(()).expect("Helper oneshot channel must work. qed");
},
async move {
go.await.expect("Helper oneshot channel must work. qed");
assert_matches!(rx.meter().read(), Readout { sent: 4, received: 0, .. });
rx.try_next().unwrap();
assert_matches!(rx.meter().read(), Readout { sent: 4, received: 1, .. });
rx.try_next().unwrap();
rx.try_next().unwrap();
assert_matches!(rx.meter().read(), Readout { sent: 4, received: 3, .. });
rx.try_next().unwrap();
assert_matches!(dbg!(rx.meter().read()), Readout { sent: 4, received: 4, .. });
}
)
});
}
use futures_timer::Delay;
use std::time::Duration;
#[test]
fn stream_and_sink() {
let (mut tx, mut rx) = channel::<Msg>(5);
block_on(async move {
futures::join!(
async move {
for i in 0..15 {
println!("Sent #{} with a backlog of {} items", i + 1, tx.meter().read());
let msg = Msg { val: i as u8 + 1u8 };
tx.send(msg).await.unwrap();
assert!(tx.meter().read().sent > 0usize);
Delay::new(Duration::from_millis(20)).await;
}
()
},
async move {
while let Some(msg) = rx.next().await {
println!("rx'd one {} with {} backlogged", msg.val, rx.meter().read());
Delay::new(Duration::from_millis(29)).await;
}
}
)
});
}
#[test]
fn failed_send_does_not_inc_sent() {
let (mut bounded, _) = channel::<Msg>(5);
let (unbounded, _) = unbounded::<Msg>();
block_on(async move {
assert!(bounded.send(Msg::default()).await.is_err());
assert!(bounded.try_send(Msg::default()).is_err());
assert_matches!(bounded.meter().read(), Readout { sent: 0, received: 0, .. });
assert!(unbounded.unbounded_send(Msg::default()).is_err());
assert_matches!(unbounded.meter().read(), Readout { sent: 0, received: 0, .. });
});
}
#[test]
fn blocked_send_is_metered() {
let (mut bounded_sender, mut bounded_receiver) = channel::<Msg>(1);
block_on(async move {
assert!(bounded_sender.send(Msg::default()).await.is_ok());
assert!(bounded_sender.send(Msg::default()).await.is_ok());
assert!(bounded_sender.try_send(Msg::default()).is_err());
assert_matches!(
bounded_sender.meter().read(),
Readout { sent: 2, received: 0, blocked: 1, .. }
);
bounded_receiver.try_next().unwrap();
assert_matches!(
bounded_receiver.meter().read(),
Readout { sent: 2, received: 1, blocked: 1, .. }
);
});
}
@@ -1,166 +0,0 @@
// Copyright 2017-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/>.
//! Metered variant of unbounded mpsc channels to be able to extract metrics.
use futures::{
channel::mpsc,
stream::Stream,
task::{Context, Poll},
};
use std::{pin::Pin, result};
use super::{measure_tof_check, CoarseInstant, MaybeTimeOfFlight, Meter};
/// Create a wrapped `mpsc::channel` pair of `MeteredSender` and `MeteredReceiver`.
pub fn unbounded<T>() -> (UnboundedMeteredSender<T>, UnboundedMeteredReceiver<T>) {
let (tx, rx) = mpsc::unbounded::<MaybeTimeOfFlight<T>>();
let shared_meter = Meter::default();
let tx = UnboundedMeteredSender { meter: shared_meter.clone(), inner: tx };
let rx = UnboundedMeteredReceiver { meter: shared_meter, inner: rx };
(tx, rx)
}
/// A receiver tracking the messages consumed by itself.
#[derive(Debug)]
pub struct UnboundedMeteredReceiver<T> {
// count currently contained messages
meter: Meter,
inner: mpsc::UnboundedReceiver<MaybeTimeOfFlight<T>>,
}
impl<T> std::ops::Deref for UnboundedMeteredReceiver<T> {
type Target = mpsc::UnboundedReceiver<MaybeTimeOfFlight<T>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for UnboundedMeteredReceiver<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> Stream for UnboundedMeteredReceiver<T> {
type Item = T;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match mpsc::UnboundedReceiver::poll_next(Pin::new(&mut self.inner), cx) {
Poll::Ready(maybe_value) => Poll::Ready(self.maybe_meter_tof(maybe_value)),
Poll::Pending => Poll::Pending,
}
}
/// Don't rely on the unreliable size hint.
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
}
impl<T> UnboundedMeteredReceiver<T> {
fn maybe_meter_tof(&mut self, maybe_value: Option<MaybeTimeOfFlight<T>>) -> Option<T> {
self.meter.note_received();
maybe_value.map(|value| {
match value {
MaybeTimeOfFlight::<T>::WithTimeOfFlight(value, tof_start) => {
// do not use `.elapsed()` of `std::time`, it may panic
// `coarsetime` does a saturating substractio for all `CoarseInstant`s
let duration = tof_start.elapsed();
self.meter.note_time_of_flight(duration);
value
},
MaybeTimeOfFlight::<T>::Bare(value) => value,
}
.into()
})
}
/// Get an updated accessor object for all metrics collected.
pub fn meter(&self) -> &Meter {
&self.meter
}
/// Attempt to receive the next item.
pub fn try_next(&mut self) -> Result<Option<T>, mpsc::TryRecvError> {
match self.inner.try_next()? {
Some(value) => Ok(self.maybe_meter_tof(Some(value))),
None => Ok(None),
}
}
}
impl<T> futures::stream::FusedStream for UnboundedMeteredReceiver<T> {
fn is_terminated(&self) -> bool {
self.inner.is_terminated()
}
}
/// The sender component, tracking the number of items
/// sent across it.
#[derive(Debug)]
pub struct UnboundedMeteredSender<T> {
meter: Meter,
inner: mpsc::UnboundedSender<MaybeTimeOfFlight<T>>,
}
impl<T> Clone for UnboundedMeteredSender<T> {
fn clone(&self) -> Self {
Self { meter: self.meter.clone(), inner: self.inner.clone() }
}
}
impl<T> std::ops::Deref for UnboundedMeteredSender<T> {
type Target = mpsc::UnboundedSender<MaybeTimeOfFlight<T>>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> std::ops::DerefMut for UnboundedMeteredSender<T> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
impl<T> UnboundedMeteredSender<T> {
fn prepare_with_tof(&self, item: T) -> MaybeTimeOfFlight<T> {
let previous = self.meter.note_sent();
let item = if measure_tof_check(previous) {
MaybeTimeOfFlight::WithTimeOfFlight(item, CoarseInstant::now())
} else {
MaybeTimeOfFlight::Bare(item)
};
item
}
/// Get an updated accessor object for all metrics collected.
pub fn meter(&self) -> &Meter {
&self.meter
}
/// Attempt to send message or fail immediately.
pub fn unbounded_send(
&self,
msg: T,
) -> result::Result<(), mpsc::TrySendError<MaybeTimeOfFlight<T>>> {
let msg = self.prepare_with_tof(msg);
self.inner.unbounded_send(msg).map_err(|e| {
self.meter.retract_sent();
e
})
}
}
+1 -1
View File
@@ -10,7 +10,7 @@ futures = "0.3.21"
futures-timer = "3.0.2"
gum = { package = "tracing-gum", path = "../gum" }
metered = { package = "prioritized-metered-channel", path = "../metered-channel" , "version" = "0.2.0" }
metered = { package = "prioritized-metered-channel", version = "0.2.0" }
# Both `sc-service` and `sc-cli` are required by runtime metrics `logger_hook()`.
sc-service = { git = "https://github.com/paritytech/substrate", branch = "master" }
-37
View File
@@ -1,37 +0,0 @@
[package]
name = "orchestra"
version = "0.0.1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
description = "Generate an orchestra of subsystems from a single struct."
repository = "https://github.com/paritytech/polkadot"
license = "MIT OR Apache-2.0"
autoexamples = false
[dependencies]
tracing = "0.1.35"
futures = "0.3"
async-trait = "0.1"
thiserror = "1"
metered = { package = "prioritized-metered-channel", version = "0.2.0", path = "../metered-channel" }
orchestra-proc-macro = { version = "0.0.1", path = "./proc-macro" }
futures-timer = "3.0.2"
pin-project = "1.0"
dyn-clonable = "0.9"
[dev-dependencies]
trybuild = "1.0.61"
rustversion = "1.0.6"
[[example]]
name = "duo"
crate-type = ["bin"]
[[example]]
name = "solo"
crate-type = ["bin"]
[features]
default = []
expand = ["orchestra-proc-macro/expand"]
-99
View File
@@ -1,99 +0,0 @@
# orchestra
The orchestra pattern is a partial actor pattern, with a global orchestrator regarding
relevant work items.
## proc-macro
The proc macro provides a convenience generator with a builder pattern,
where at it's core it creates and spawns a set of subsystems, which are purely
declarative.
```rust
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
pub struct Opera {
#[subsystem(MsgA, sends: [MsgB])]
sub_a: AwesomeSubSysA,
#[subsystem(MsgB, sends: [MsgA])]
sub_b: AwesomeSubSysB,
}
```
* Each subsystem is annotated with `#[subsystem(_)]` where `MsgA` respectively `MsgB` are the messages
being consumed by that particular subsystem. Each of those subsystems is required to implement the subsystem
trait with the correct trait bounds. Commonly this is achieved
by using `#[subsystem]` and `#[contextbounds]` macro.
* `#[contextbounds(Foo, error=Yikes, prefix=wherethetraitsat)]` can applied to `impl`-blocks and `fn`-blocks. It will add additional trait bounds for the generic `Context` with `Context: FooContextTrait` for `<Context as FooContextTrait>::Sender: FooSenderTrait` besides a few more. Note that `Foo` here references the name of the subsystem as declared in `#[orchestra(..)]` macro.
* `#[subsystem(Foo, error=Yikes, prefix=wherethetraitsat)]` is a extension to the above, implementing `trait Subsystem<Context, Yikes>`.
* `error=` tells the orchestra to use the user provided
error type, if not provided a builtin one is used. Note that this is the one error type used throughout all calls, so make sure it does impl `From<E>` for all other error types `E` that are relevant to your application.
* `event=` declares an external event type, that injects certain events
into the orchestra, without participating in the subsystem pattern.
* `signal=` defines a signal type to be used for the orchestra. This is a shared "tick" or "clock" for all subsystems.
* `gen=` defines a wrapping `enum` type that is used to wrap all messages that can be consumed by _any_ subsystem.
```rust
/// Execution context, always required.
pub struct DummyCtx;
/// Task spawner, always required
/// and must implement `trait orchestra::Spawner`.
pub struct DummySpawner;
fn main() {
let _orchestra = Opera::builder()
.sub_a(AwesomeSubSysA::default())
.sub_b(AwesomeSubSysB::default())
.spawner(DummySpawner)
.build();
}
```
In the shown `main`, the orchestra is created by means of a generated, compile time erroring
builder pattern.
The builder requires all subsystems, baggage fields (additional struct data) and spawner to be
set via the according setter method before `build` method could even be called. Failure to do
such an initialization will lead to a compile error. This is implemented by encoding each
builder field in a set of so called `state generics`, meaning that each field can be either
`Init<T>` or `Missing<T>`, so each setter translates a state from `Missing` to `Init` state
for the specific struct field. Therefore, if you see a compile time error that blames about
`Missing` where `Init` is expected it usually means that some subsystems or baggage fields were
not set prior to the `build` call.
To exclude subsystems from such a check, one can set `wip` attribute on some subsystem that
is not ready to be included in the Orchestra:
```rust
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
pub struct Opera {
#[subsystem(MsgA, sends: MsgB)]
sub_a: AwesomeSubSysA,
#[subsystem(MsgB, sends: MsgA), wip]
sub_b: AwesomeSubSysB, // This subsystem will not be required nor allowed to be set
}
```
Baggage fields can be initialized more than one time, however, it is not true for subsystems:
subsystems must be initialized only once (another compile time check) or be _replaced_ by
a special setter like method `replace_<subsystem>`.
A task spawner and subsystem context are required to be defined with `Spawner` and respectively `SubsystemContext` implemented.
## Debugging
As always, debugging is notoriously annoying with bugged proc-macros.
Therefore [`expander`](https://github.com/drahnr/expander) is employed to yield better
error messages. Enable with `--feature=orchestra/expand`.
## License
Licensed under either of
* Apache License, Version 2.0, (LICENSE-APACHE or <http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license (LICENSE-MIT or <http://opensource.org/licenses/MIT>)
at your option.
-21
View File
@@ -1,21 +0,0 @@
# Limit outgoing messages
## Status
Accepted + implemented.
## Context
Previously, there was no way to limit and hence reason about a subset of subsystems, and if they form a cycle. Limiting the outgoing message types is a first step to create respective graphs and use classic graph algorithms to detect those and leave it to the user to resolve these.
## Decision
Annotate the `#[orchestra]` inner `#[subsystem(..)]` annotation
with an aditional set of outgoing messages and enforce this via more fine grained trait bounds on the `Sender` and `<Context>::Sender` bounds.
## Consequences
* A graph will be spawn for every compilation under the `OUT_DIR` of the crate where `#[orchestra]` is specified.
* Each subsystem has a consuming message which is often referred to as generic `M` (no change on that, is as before), but now we have trait `AssociateOutgoing { type OutgoingMessages = ..; }` which defines an outgoing helper `enum` that is generated with an ident constructed as `${Subsystem}OutgoingMessages` where `${Subsystem}` is the subsystem identifier as used in the orchestra declaration. `${Subsystem}OutgoingMessages` is used throughout everywhere to constrain the outgoing messages (commonly referred to as `OutgoingMessage` generic bounded by `${Subsystem}OutgoingMessages: From<OutgoingMessage>` or `::OutgoingMessages: From`. It's what allows the construction of the graph and compile time verification.
* `${Subsystem}SenderTrait` and `${Subsystem}ContextTrait` are accumulation traits or wrapper traits, that combine over all annotated M or `OutgoingMessages` from the orchestra declaration or their respective outgoing types. It is usage convenience and assures consistency within a subsystem while also maintaining a single source of truth for which messages can be sent by a particular subsystem. Note that this is sidestepped for the test subsystem, which may consume `gen=AllMessages`, the global message wrapper type.
* `Job`-based subsystems, being on their way out, are patched, but they now are generic over the `Sender` type, leaking that type.
-106
View File
@@ -1,106 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(dead_code)] // orchestra events are not used
//! A dummy to be used with cargo expand
use orchestra::{self as orchestra, Spawner, *};
use std::{collections::HashMap, sync::Arc};
mod misc;
pub use self::misc::*;
/// Concrete subsystem implementation for `MsgStrukt` msg type.
#[derive(Default)]
pub struct AwesomeSubSys;
#[orchestra::subsystem(Awesome, error=Yikes)]
impl<Context> AwesomeSubSys {
fn start(self, mut ctx: Context) -> SpawnedSubsystem<Yikes> {
let mut sender = ctx.sender().clone();
ctx.spawn(
"AwesomeSubsys",
Box::pin(async move {
sender.send_message(Plinko).await;
}),
)
.unwrap();
unimplemented!("starting yay!")
}
}
#[derive(Default)]
pub struct Fortified;
#[orchestra::subsystem(GoblinTower, error=Yikes)]
impl<Context> Fortified {
fn start(self, mut ctx: Context) -> SpawnedSubsystem<Yikes> {
let mut sender = ctx.sender().clone();
ctx.spawn(
"GoblinTower",
Box::pin(async move {
sender.send_message(MsgStrukt(8u8)).await;
}),
)
.unwrap();
unimplemented!("welcum")
}
}
#[orchestra(signal=SigSigSig, event=EvX, error=Yikes, gen=AllMessages)]
struct Duo<T, U, V, W> {
#[subsystem(consumes: MsgStrukt, sends: [Plinko])]
sub0: Awesome,
#[subsystem(blocking, consumes: Plinko, sends: [MsgStrukt])]
plinkos: GoblinTower,
i_like_pi: f64,
i_like_tuple: (f64, f64),
i_like_generic: Arc<T>,
i_like_hash: HashMap<(U, V), Arc<W>>,
}
fn main() {
use futures::{executor, pin_mut};
executor::block_on(async move {
let (orchestra, _handle): (Duo<_, f64, u32, f32, f64>, _) = Duo::builder()
.sub0(AwesomeSubSys::default())
.plinkos(Fortified::default())
.i_like_pi(::std::f64::consts::PI)
.i_like_tuple((::std::f64::consts::PI, ::std::f64::consts::PI))
.i_like_generic(Arc::new(42.0))
.i_like_hash(HashMap::new())
.spawner(DummySpawner)
.build()
.unwrap();
assert_eq!(orchestra.i_like_pi.floor() as i8, 3);
assert_eq!(orchestra.i_like_generic.floor() as i8, 42);
assert_eq!(orchestra.i_like_hash.len() as i8, 0);
let orchestra_fut = orchestra
.running_subsystems
.into_future()
.timeout(std::time::Duration::from_millis(300))
.fuse();
pin_mut!(orchestra_fut);
orchestra_fut.await
});
}
-84
View File
@@ -1,84 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use orchestra::{Spawner, *};
#[derive(Debug, Clone, Copy)]
pub enum SigSigSig {
Conclude,
Foo,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
/// The external event.
#[derive(Debug, Clone)]
pub struct EvX;
impl EvX {
pub fn focus<'a, T>(&'a self) -> Result<EvX, ()> {
unimplemented!("focus")
}
}
#[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<orchestra::OrchestraError> for Yikes {
fn from(_: orchestra::OrchestraError) -> Yikes {
Yikes
}
}
impl From<orchestra::mpsc::SendError> for Yikes {
fn from(_: orchestra::mpsc::SendError) -> Yikes {
Yikes
}
}
#[derive(Debug, Clone)]
pub struct MsgStrukt(pub u8);
#[derive(Debug, Clone, Copy)]
pub struct Plinko;
-74
View File
@@ -1,74 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#![allow(dead_code)] // orchestra events are not used
//! A minimal demo to be used with cargo expand.
use orchestra::{self as orchestra, Spawner, *};
mod misc;
pub use self::misc::*;
#[orchestra(signal=SigSigSig, event=EvX, error=Yikes, gen=AllMessages)]
struct Solo<T> {
#[subsystem(consumes: Plinko, sends: [MsgStrukt])]
goblin_tower: GoblinTower,
}
#[derive(Default)]
pub struct Fortified;
#[orchestra::subsystem(GoblinTower, error=Yikes)]
impl<Context> Fortified {
fn start(self, mut ctx: Context) -> SpawnedSubsystem<Yikes> {
let mut sender = ctx.sender().clone();
ctx.spawn(
"GoblinTower",
Box::pin(async move {
sender.send_message(MsgStrukt(8u8)).await;
}),
)
.unwrap();
unimplemented!("welcum")
}
}
async fn setup() {
let builder = Solo::builder();
let builder = builder.goblin_tower(Fortified::default());
let builder = builder.spawner(DummySpawner);
let (orchestra, _handle): (Solo<_>, _) = builder.build().unwrap();
let orchestra_fut = orchestra
.running_subsystems
.into_future()
.timeout(std::time::Duration::from_millis(300))
.fuse();
futures::pin_mut!(orchestra_fut);
orchestra_fut.await;
}
fn assert_t_impl_trait_send<T: Send>(_: &T) {}
fn main() {
let x = setup();
assert_t_impl_trait_send(&x);
futures::executor::block_on(x);
}
@@ -1,38 +0,0 @@
[package]
name = "orchestra-proc-macro"
version = "0.0.1"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
description = "Generate an orchestra of subsystems from a single annotated struct definition."
repository = "https://github.com/paritytech/polkadot"
license = "MIT OR Apache-2.0"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.95", features = ["full", "extra-traits"] }
quote = "1.0.20"
proc-macro2 = "1.0.43"
proc-macro-crate = "1.1.3"
expander = { version = "0.0.6", default-features = false }
petgraph = "0.6.0"
itertools = { version = "0.10.3" }
[dev-dependencies]
assert_matches = "1.5"
orchestra = { path = "../" }
thiserror = "1"
tracing = "0.1"
[features]
default = [] # enable "graph" by default, blocked by <https://github.com/paritytech/ci_cd/issues/433>
# write the expanded version to a `orchestra-expansion.[a-f0-9]{10}.rs`
# in the `OUT_DIR` as defined by `cargo` for the `expander` crate.
expand = []
# Create directional message consuming / outgoing graph.
# Generates: `${OUT_DIR}/${orchestra|lowercase}-subsystem-messaging.dot`
graph = []
@@ -1,3 +0,0 @@
fn main() {
// populate OUT_DIR
}
@@ -1,432 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use quote::ToTokens;
use syn::{Ident, Path};
use petgraph::{graph::NodeIndex, Graph};
use std::collections::{hash_map::RandomState, HashMap, HashSet};
use super::*;
/// Representation of all subsystem connections
pub(crate) struct ConnectionGraph<'a> {
/// Graph of connected subsystems
///
/// The graph represents a subsystem as a node or `NodeIndex`
/// and edges are messages sent, directed from the sender to
/// the receiver of the message.
pub(crate) graph: Graph<Ident, Path>,
/// Cycles within the graph
#[cfg_attr(not(feature = "graph"), allow(dead_code))]
pub(crate) sccs: Vec<Vec<NodeIndex>>,
/// Messages that are never being sent (and by which subsystem), but are consumed
/// Maps the message `Path` to the subsystem `Ident` represented by `NodeIndex`.
#[cfg_attr(not(feature = "graph"), allow(dead_code))]
pub(crate) unsent_messages: HashMap<&'a Path, (&'a Ident, NodeIndex)>,
/// Messages being sent (and by which subsystem), but not consumed by any subsystem
/// Maps the message `Path` to the subsystem `Ident` represented by `NodeIndex`.
#[cfg_attr(not(feature = "graph"), allow(dead_code))]
pub(crate) unconsumed_messages: HashMap<&'a Path, Vec<(&'a Ident, NodeIndex)>>,
}
impl<'a> ConnectionGraph<'a> {
/// Generates all subsystem types and related accumulation traits.
pub(crate) fn construct(ssfs: &'a [SubSysField]) -> Self {
// create a directed graph with all the subsystems as nodes and the messages as edges
// key is always the message path, values are node indices in the graph and the subsystem generic identifier
// store the message path and the source sender, both in the graph as well as identifier
let mut outgoing_lut = HashMap::<&Path, Vec<(&Ident, NodeIndex)>>::with_capacity(128);
// same for consuming the incoming messages
let mut consuming_lut = HashMap::<&Path, (&Ident, NodeIndex)>::with_capacity(128);
let mut graph = Graph::<Ident, Path>::new();
// prepare the full index of outgoing and source subsystems
for ssf in ssfs {
let node_index = graph.add_node(ssf.generic.clone());
for outgoing in ssf.messages_to_send.iter() {
outgoing_lut.entry(outgoing).or_default().push((&ssf.generic, node_index));
}
if let Some(_first_consument) =
consuming_lut.insert(&ssf.message_to_consume, (&ssf.generic, node_index))
{
// bail, two subsystems consuming the same message
}
}
for (message_ty, (_consuming_subsystem_ident, consuming_node_index)) in consuming_lut.iter()
{
// match the outgoing ones that were registered above with the consumed message
if let Some(origin_subsystems) = outgoing_lut.get(message_ty) {
for (_origin_subsystem_ident, sending_node_index) in origin_subsystems.iter() {
graph.add_edge(
*sending_node_index,
*consuming_node_index,
(*message_ty).clone(),
);
}
}
}
// extract unsent and unreceived messages
let outgoing_set = HashSet::<_, RandomState>::from_iter(outgoing_lut.keys().cloned());
let consuming_set = HashSet::<_, RandomState>::from_iter(consuming_lut.keys().cloned());
let mut unsent_messages = consuming_lut;
unsent_messages.retain(|k, _v| !outgoing_set.contains(k));
let mut unconsumed_messages = outgoing_lut;
unconsumed_messages.retain(|k, _v| !consuming_set.contains(k));
let scc = Self::extract_scc(&graph);
Self { graph, sccs: scc, unsent_messages, unconsumed_messages }
}
/// Extract the strongly connected components (`scc`) which each
/// includes at least one cycle each.
fn extract_scc(graph: &Graph<Ident, Path>) -> Vec<Vec<NodeIndex>> {
use petgraph::visit::EdgeRef;
// there is no guarantee regarding the node indices in the individual sccs
let sccs = petgraph::algo::kosaraju_scc(&graph);
let sccs = Vec::from_iter(sccs.into_iter().filter(|scc| {
match scc.len() {
1 => {
// contains sccs of length one,
// which do not exists, might be an upstream bug?
let node_idx = scc[0];
graph
.edges_directed(node_idx, petgraph::Direction::Outgoing)
.find(|edge| edge.target() == node_idx)
.is_some()
},
0 => false,
_n => true,
}
}));
match sccs.len() {
0 => eprintln!("✅ Found no strongly connected components, hence no cycles exist"),
1 => eprintln!(
"⚡ Found 1 strongly connected component which includes at least one cycle"
),
n => eprintln!(
"⚡ Found {n} strongly connected components which includes at least one cycle each"
),
}
let greek_alphabet = greek_alphabet();
for (scc_idx, scc) in sccs.iter().enumerate() {
let scc_tag = greek_alphabet.get(scc_idx).copied().unwrap_or('_');
let mut acc = Vec::with_capacity(scc.len());
assert!(scc.len() > 0);
let mut node_idx = scc[0].clone();
let print_idx = scc_idx + 1;
// track which ones were visited and which step
// the step is required to truncate the output
// which is required to greedily find a cycle in the strongly connected component
let mut visited = HashMap::new();
for step in 0..scc.len() {
if let Some(edge) =
graph.edges_directed(node_idx, petgraph::Direction::Outgoing).find(|edge| {
scc.iter().find(|&scc_node_idx| *scc_node_idx == edge.target()).is_some()
}) {
let next = edge.target();
visited.insert(node_idx, step);
let subsystem_name = &graph[node_idx].to_string();
let message_name = &graph[edge.id()].to_token_stream().to_string();
acc.push(format!("{subsystem_name} ~~{{{message_name:?}}}~~> "));
node_idx = next;
if let Some(step) = visited.get(&next) {
// we've been there, so there is a cycle
// cut off the extra tail
assert!(acc.len() >= *step);
acc.drain(..step);
// there might be more cycles in this cluster,
// but for they are ignored, the graph shows
// the entire strongly connected component.
break
}
} else {
eprintln!("cycle({print_idx:03}) ∈ {scc_tag}: Missing connection in hypothesized cycle after {step} steps, this is a bug 🐛");
break
}
}
let acc = String::from_iter(acc);
eprintln!("cycle({print_idx:03}) ∈ {scc_tag}: {acc} *");
}
sccs
}
/// Render a graphviz (aka dot graph) to a file.
///
/// Cycles are annotated with the lower
#[cfg(feature = "graph")]
pub(crate) fn graphviz(self, dest: &mut impl std::io::Write) -> std::io::Result<()> {
use self::graph_helpers::*;
use petgraph::{
dot::{self, Dot},
visit::{EdgeRef, IntoEdgeReferences, IntoNodeReferences},
};
// only write the grap content, we want a custom color scheme
let config = &[
dot::Config::GraphContentOnly,
dot::Config::EdgeNoLabel,
dot::Config::NodeNoLabel,
][..];
let Self { mut graph, unsent_messages, unconsumed_messages, sccs } = self;
// the greek alphabet, lowercase
let greek_alphabet = greek_alphabet();
const COLOR_SCHEME_N: usize = 10; // rdylgn10
// Adding more than 10, is _definitely_ too much visual clutter in the graph.
const UPPER_BOUND: usize = 10;
assert!(UPPER_BOUND <= GREEK_ALPHABET_SIZE);
assert!(UPPER_BOUND <= COLOR_SCHEME_N);
let n = sccs.len();
let n = if n > UPPER_BOUND {
eprintln!("Too many ({n}) strongly connected components, only annotating the first {UPPER_BOUND}");
UPPER_BOUND
} else {
n
};
// restructure for lookups
let mut scc_lut = HashMap::<NodeIndex, HashSet<char>>::with_capacity(n);
// lookup the color index (which is equiv to the index in the cycle set vector _plus one_)
// based on the cycle_tag (the greek char)
let mut color_lut = HashMap::<char, usize>::with_capacity(COLOR_SCHEME_N);
for (scc_idx, scc) in sccs.into_iter().take(UPPER_BOUND).enumerate() {
for node_idx in scc {
let _ = scc_lut.entry(node_idx).or_default().insert(greek_alphabet[scc_idx]);
}
color_lut.insert(greek_alphabet[scc_idx], scc_idx + 1);
}
let color_lut = &color_lut;
// Adding nodes is ok, the `NodeIndex` is append only as long
// there are no removals.
// Depict sink for unconsumed messages
let unconsumed_idx = graph.add_node(quote::format_ident!("SENT_TO_NONONE"));
for (message_name, subsystems) in unconsumed_messages {
// iterate over all subsystems that send such a message
for (_sub_name, sub_node_idx) in subsystems {
graph.add_edge(sub_node_idx, unconsumed_idx, message_name.clone());
}
}
// depict source of unsent message, this is legit when
// integrated with an external source, and sending messages based
// on that
let unsent_idx = graph.add_node(quote::format_ident!("NEVER_SENT_ANYWHERE"));
for (message_name, (_sub_name, sub_node_idx)) in unsent_messages {
graph.add_edge(unsent_idx, sub_node_idx, message_name.clone());
}
let unsent_node_label = r#"label="✨",fillcolor=black,shape=doublecircle,style=filled,fontname="NotoColorEmoji""#;
let unconsumed_node_label = r#"label="💀",fillcolor=black,shape=doublecircle,style=filled,fontname="NotoColorEmoji""#;
let edge_attr = |_graph: &Graph<Ident, Path>,
edge: <&Graph<Ident, Path> as IntoEdgeReferences>::EdgeRef|
-> String {
let source = edge.source();
let sink = edge.target();
let message_name =
edge.weight().get_ident().expect("Must have a trailing identifier. qed");
// use the intersection only, that's the set of cycles the edge is part of
if let Some(edge_intersecting_scc_tags) = scc_lut.get(&source).and_then(|source_set| {
scc_lut.get(&sink).and_then(move |sink_set| {
let intersection =
HashSet::<_, RandomState>::from_iter(source_set.intersection(sink_set));
if intersection.is_empty() {
None
} else {
Some(intersection)
}
})
}) {
if edge_intersecting_scc_tags.len() != 1 {
unreachable!("Strongly connected components are disjunct by definition. qed");
}
let scc_tag = edge_intersecting_scc_tags.iter().next().unwrap();
let color = get_color_by_tag(scc_tag, color_lut);
let scc_tag_str = cycle_tags_to_annotation(edge_intersecting_scc_tags, color_lut);
format!(
r#"color="{color}",fontcolor="{color}",xlabel=<{scc_tag_str}>,label="{message_name}""#,
)
} else {
format!(r#"label="{message_name}""#,)
}
};
let node_attr =
|_graph: &Graph<Ident, Path>,
(node_index, subsystem_name): <&Graph<Ident, Path> as IntoNodeReferences>::NodeRef|
-> String {
if node_index == unsent_idx {
unsent_node_label.to_owned().clone()
} else if node_index == unconsumed_idx {
unconsumed_node_label.to_owned().clone()
} else if let Some(edge_intersecting_scc_tags) = scc_lut.get(&node_index) {
if edge_intersecting_scc_tags.len() != 1 {
unreachable!(
"Strongly connected components are disjunct by definition. qed"
);
};
let scc_tag = edge_intersecting_scc_tags.iter().next().unwrap();
let color = get_color_by_tag(scc_tag, color_lut);
let scc_tag_str =
cycle_tags_to_annotation(edge_intersecting_scc_tags, color_lut);
format!(
r#"color="{color}",fontcolor="{color}",xlabel=<{scc_tag_str}>,label="{subsystem_name}""#,
)
} else {
format!(r#"label="{subsystem_name}""#)
}
};
let dot = Dot::with_attr_getters(
&graph, config, &edge_attr, // with state, the reference is a trouble maker
&node_attr,
);
dest.write_all(
format!(
r#"digraph {{
node [colorscheme={}]
{:?}
}}"#,
color_scheme(),
&dot
)
.as_bytes(),
)?;
Ok(())
}
}
const GREEK_ALPHABET_SIZE: usize = 24;
fn greek_alphabet() -> [char; GREEK_ALPHABET_SIZE] {
let mut alphabet = ['\u{03B1}'; 24];
alphabet
.iter_mut()
.enumerate()
// closure should never return `None`,
// but rather safe than sorry
.for_each(|(i, c)| {
*c = char::from_u32(*c as u32 + i as u32).unwrap();
});
alphabet
}
#[cfg(feature = "graph")]
mod graph_helpers {
use super::HashMap;
pub(crate) const fn color_scheme() -> &'static str {
"rdylgn10"
}
pub(crate) fn get_color_by_idx(color_idx: usize) -> String {
let scheme = color_scheme();
format!("/{scheme}/{color_idx}")
}
pub(crate) fn get_color_by_tag(scc_tag: &char, color_lut: &HashMap<char, usize>) -> String {
get_color_by_idx(color_lut.get(scc_tag).copied().unwrap_or_default())
}
/// A node can be member of multiple cycles,
/// but only of one strongly connected component.
pub(crate) fn cycle_tags_to_annotation<'a>(
cycle_tags: impl IntoIterator<Item = &'a char>,
color_lut: &HashMap<char, usize>,
) -> String {
// Must use fully qualified syntax:
// <https://github.com/rust-lang/rust/issues/48919>
let cycle_annotation = String::from_iter(itertools::Itertools::intersperse(
cycle_tags.into_iter().map(|scc_tag| {
let color = get_color_by_tag(scc_tag, color_lut);
format!(r#"<B><FONT COLOR="{color}">{scc_tag}</FONT></B>"#)
}),
",".to_owned(),
));
cycle_annotation
}
}
#[cfg(test)]
mod tests {
// whenever this starts working, we should consider
// replacing the all caps idents with something like
// the below.
// <https://rust-lang.github.io/rfcs/2457-non-ascii-idents.html>
//
// For now the rendering is modified, the ident is a placeholder.
#[test]
#[should_panic]
fn check_ident() {
let _ident = quote::format_ident!("x💀x");
}
#[test]
fn kosaraju_scc_check_nodes_cannot_be_part_of_two_clusters() {
let mut graph = petgraph::graph::DiGraph::<char, &str>::new();
let a_idx = graph.add_node('A');
let b_idx = graph.add_node('B');
let c_idx = graph.add_node('C');
let d_idx = graph.add_node('D');
let e_idx = graph.add_node('E');
let f_idx = graph.add_node('F');
graph.add_edge(a_idx, b_idx, "10");
graph.add_edge(b_idx, c_idx, "11");
graph.add_edge(c_idx, a_idx, "12");
graph.add_edge(a_idx, d_idx, "20");
graph.add_edge(d_idx, c_idx, "21");
graph.add_edge(b_idx, e_idx, "30");
graph.add_edge(e_idx, c_idx, "31");
graph.add_edge(c_idx, f_idx, "40");
let mut sccs = dbg!(petgraph::algo::kosaraju_scc(&graph));
dbg!(graph);
sccs.sort_by(|a, b| {
if a.len() < b.len() {
std::cmp::Ordering::Greater
} else {
std::cmp::Ordering::Less
}
});
assert_eq!(sccs.len(), 2); // `f` and everything else
assert_eq!(sccs[0].len(), 5); // every node but `f`
}
}
@@ -1,759 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use quote::{format_ident, quote};
use syn::{parse_quote, Path, PathSegment, TypePath};
use super::*;
fn recollect_without_idx<T: Clone>(x: &[T], idx: usize) -> Vec<T> {
let mut v = Vec::<T>::with_capacity(x.len().saturating_sub(1));
v.extend(x.iter().take(idx).cloned());
v.extend(x.iter().skip(idx + 1).cloned());
v
}
/// Implement a builder pattern for the `Orchestra`-type,
/// which acts as the gateway to constructing the orchestra.
///
/// Elements tagged with `wip` are not covered here.
pub(crate) fn impl_builder(info: &OrchestraInfo) -> proc_macro2::TokenStream {
let orchestra_name = info.orchestra_name.clone();
let builder = format_ident!("{}Builder", orchestra_name);
let handle = format_ident!("{}Handle", orchestra_name);
let connector = format_ident!("{}Connector", orchestra_name);
let subsystem_ctx_name = format_ident!("{}SubsystemContext", orchestra_name);
let subsystem_name = &info.subsystem_names_without_wip();
let subsystem_generics = &info.subsystem_generic_types();
let consumes = &info.consumes_without_wip();
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_name = &info.baggage_names();
let baggage_generic_ty = &info.baggage_generic_types();
// State generics that are used to encode each field's status (Init/Missing)
let baggage_passthrough_state_generics = baggage_name
.iter()
.enumerate()
.map(|(idx, _)| format_ident!("InitStateBaggage{}", idx))
.collect::<Vec<_>>();
let subsystem_passthrough_state_generics = subsystem_name
.iter()
.enumerate()
.map(|(idx, _)| format_ident!("InitStateSubsystem{}", idx))
.collect::<Vec<_>>();
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<_>>();
// Helpers to use within quote! macros
let spawner_where_clause: syn::TypeParam = parse_quote! {
S: #support_crate ::Spawner
};
// Field names and real types
let field_name = subsystem_name.iter().chain(baggage_name.iter()).collect::<Vec<_>>();
let field_type = subsystem_generics
.iter()
.map(|ident| {
syn::Type::Path(TypePath {
qself: None,
path: Path::from(PathSegment::from(ident.clone())),
})
})
.chain(info.baggage().iter().map(|bag| bag.field_ty.clone()))
.collect::<Vec<_>>();
// Setters logic
// For each setter we need to leave the remaining fields untouched and
// remove the field that we are fixing in this setter
// For subsystems `*_with` and `replace_*` setters are needed.
let subsystem_specific_setters =
info.subsystems().iter().filter(|ssf| !ssf.wip).enumerate().map(|(idx, ssf)| {
let field_name = &ssf.name;
let field_type = &ssf.generic;
let subsystem_consumes = &ssf.message_to_consume;
// Remove state generic for the item to be replaced. It sufficient to know `field_type` for
// that since we always move from `Init<#field_type>` to `Init<NEW>`.
let impl_subsystem_state_generics = recollect_without_idx(&subsystem_passthrough_state_generics[..], idx);
let field_name_with = format_ident!("{}_with", field_name);
let field_name_replace = format_ident!("replace_{}", field_name);
// In a setter we replace `Uninit<T>` with `Init<T>` leaving all other
// types as they are, as such they will be free generics.
let mut current_state_generics = subsystem_passthrough_state_generics
.iter()
.map(|subsystem_state_generic_ty| parse_quote!(#subsystem_state_generic_ty))
.collect::<Vec<syn::GenericArgument>>();
current_state_generics[idx] = parse_quote! { Missing<#field_type> };
// Generics that will be present after initializing a specific `Missing<_>` field.
let mut post_setter_state_generics = current_state_generics.clone();
post_setter_state_generics[idx] = parse_quote! { Init<#field_type> };
let mut post_replace_state_generics = current_state_generics.clone();
post_replace_state_generics[idx] = parse_quote! { Init<NEW> };
// All fields except the one we update with the new argument
// see the loop below.
let to_keep_subsystem_name = recollect_without_idx(&subsystem_name[..], idx);
let subsystem_sender_trait = format_ident!("{}SenderTrait", field_type);
let _subsystem_ctx_trait = format_ident!("{}ContextTrait", field_type);
let builder_where_clause = quote!{
#field_type : #support_crate::Subsystem< #subsystem_ctx_name< #subsystem_consumes >, #error_ty>,
< #subsystem_ctx_name < #subsystem_consumes > as #support_crate :: SubsystemContext>::Sender:
#subsystem_sender_trait,
};
// Create the field init `fn`
quote! {
impl <InitStateSpawner, #field_type, #( #impl_subsystem_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder <InitStateSpawner, #( #current_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
#builder_where_clause
{
/// Specify the subsystem in the builder directly
pub fn #field_name (self, var: #field_type ) ->
#builder <InitStateSpawner, #( #post_setter_state_generics, )* #( #baggage_passthrough_state_generics, )*>
{
#builder {
#field_name: Init::< #field_type >::Value(var),
#(
#to_keep_subsystem_name: self. #to_keep_subsystem_name,
)*
#(
#baggage_name: self. #baggage_name,
)*
spawner: self.spawner,
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
/// Specify the the initialization function for a subsystem
pub fn #field_name_with<'a, F>(self, subsystem_init_fn: F ) ->
#builder <InitStateSpawner, #( #post_setter_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
F: 'static + Send + FnOnce(#handle) ->
::std::result::Result<#field_type, #error_ty>,
{
let boxed_func = Init::<#field_type>::Fn(
Box::new(subsystem_init_fn) as SubsystemInitFn<#field_type>
);
#builder {
#field_name: boxed_func,
#(
#to_keep_subsystem_name: self. #to_keep_subsystem_name,
)*
#(
#baggage_name: self. #baggage_name,
)*
spawner: self.spawner,
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
}
impl <InitStateSpawner, #field_type, #( #impl_subsystem_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder <InitStateSpawner, #( #post_setter_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
#builder_where_clause
{
/// Replace a subsystem by another implementation for the
/// consumable message type.
pub fn #field_name_replace<NEW, F>(self, gen_replacement_fn: F)
-> #builder <InitStateSpawner, #( #post_replace_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
#field_type: 'static,
F: 'static + Send + FnOnce(#field_type) -> NEW,
NEW: #support_crate ::Subsystem<#subsystem_ctx_name< #subsystem_consumes >, #error_ty>,
{
let replacement: Init<NEW> = match self.#field_name {
Init::Fn(fx) =>
Init::<NEW>::Fn(Box::new(move |handle: #handle| {
let orig = fx(handle)?;
Ok(gen_replacement_fn(orig))
})),
Init::Value(val) =>
Init::Value(gen_replacement_fn(val)),
};
#builder {
#field_name: replacement,
#(
#to_keep_subsystem_name: self. #to_keep_subsystem_name,
)*
#(
#baggage_name: self. #baggage_name,
)*
spawner: self.spawner,
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
}
}
});
// Produce setters for all baggage fields as well
let baggage_specific_setters = info.baggage().iter().enumerate().map(|(idx, bag_field)| {
// Baggage fields follow subsystems
let fname = &bag_field.field_name;
let field_type = &bag_field.field_ty;
let impl_baggage_state_generics = recollect_without_idx(&baggage_passthrough_state_generics[..], idx);
let to_keep_baggage_name = recollect_without_idx(&baggage_name[..], idx);
let mut pre_setter_generics = baggage_passthrough_state_generics
.iter()
.map(|gen_ty| parse_quote!(#gen_ty))
.collect::<Vec<syn::GenericArgument>>();
pre_setter_generics[idx] = parse_quote! { Missing<#field_type> };
let mut post_setter_generics = pre_setter_generics.clone();
post_setter_generics[idx] = parse_quote! { Init<#field_type> };
// Baggage can also be generic, so we need to include that to a signature
let preserved_baggage_generics = &bag_field.generic_types;
quote! {
impl <InitStateSpawner, #( #preserved_baggage_generics, )* #( #subsystem_passthrough_state_generics, )* #( #impl_baggage_state_generics, )* >
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #pre_setter_generics, )* >
{
/// Specify the baggage in the builder when it was not initialized before
pub fn #fname (self, var: #field_type ) ->
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #post_setter_generics, )* >
{
#builder {
#fname: Init::<#field_type>::Value(var),
#(
#subsystem_name: self. #subsystem_name,
)*
#(
#to_keep_baggage_name: self. #to_keep_baggage_name,
)*
spawner: self.spawner,
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
}
impl <InitStateSpawner, #( #preserved_baggage_generics, )* #( #subsystem_passthrough_state_generics, )* #( #impl_baggage_state_generics, )* >
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #post_setter_generics, )* > {
/// Specify the baggage in the builder when it has been previously initialized
pub fn #fname (self, var: #field_type ) ->
#builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #post_setter_generics, )* >
{
#builder {
#fname: Init::<#field_type>::Value(var),
#(
#subsystem_name: self. #subsystem_name,
)*
#(
#to_keep_baggage_name: self. #to_keep_baggage_name,
)*
spawner: self.spawner,
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
}
}
});
let event = &info.extern_event_ty;
let initialized_builder = format_ident!("Initialized{}", builder);
// The direct generics as expected by the `Orchestra<_,_,..>`, without states
let initialized_builder_generics = quote! {
S, #( #baggage_generic_ty, )* #( #subsystem_generics, )*
};
let builder_where_clause = info
.subsystems()
.iter()
.map(|ssf| {
let field_type = &ssf.generic;
let consumes = &ssf.message_to_consume;
let subsystem_sender_trait = format_ident!("{}SenderTrait", ssf.generic);
let subsystem_ctx_trait = format_ident!("{}ContextTrait", ssf.generic);
quote! {
#field_type:
#support_crate::Subsystem< #subsystem_ctx_name < #consumes>, #error_ty>,
<#subsystem_ctx_name< #consumes > as #subsystem_ctx_trait>::Sender:
#subsystem_sender_trait,
#subsystem_ctx_name< #consumes >:
#subsystem_ctx_trait,
}
})
.fold(TokenStream::new(), |mut ts, addendum| {
ts.extend(addendum);
ts
});
let mut ts = quote! {
/// Convenience alias.
type SubsystemInitFn<T> = Box<dyn FnOnce(#handle) -> ::std::result::Result<T, #error_ty> + Send + 'static>;
/// Type for the initialized field of the orchestra builder
pub enum Init<T> {
/// Defer initialization to a point where the `handle` is available.
Fn(SubsystemInitFn<T>),
/// Directly initialize the subsystem with the given subsystem type `T`.
/// Also used for baggage fields
Value(T),
}
/// Type marker for the uninitialized field of the orchestra builder.
/// `PhantomData` is used for type hinting when creating uninitialized
/// builder, e.g. to avoid specifying the generics when instantiating
/// the `FooBuilder` when calling `Foo::builder()`
#[derive(Debug)]
pub struct Missing<T>(::core::marker::PhantomData<T>);
/// Trait used to mark fields status in a builder
trait OrchestraFieldState<T> {}
impl<T> OrchestraFieldState<T> for Init<T> {}
impl<T> OrchestraFieldState<T> for Missing<T> {}
impl<T> ::std::default::Default for Missing<T> {
fn default() -> Self {
Missing::<T>(::core::marker::PhantomData::<T>::default())
}
}
impl<S #(, #baggage_generic_ty )*> #orchestra_name <S #(, #baggage_generic_ty)*>
where
#spawner_where_clause,
{
/// Create a new orchestra utilizing the builder.
pub fn builder< #( #subsystem_generics),* >() ->
#builder<Missing<S> #(, Missing< #field_type > )* >
where
#builder_where_clause
{
#builder :: new()
}
}
};
ts.extend(quote! {
/// Handle for an orchestra.
pub type #handle = #support_crate ::metered::MeteredSender< #event >;
/// External connector.
pub struct #connector {
/// Publicly accessible handle, to be used for setting up
/// components that are _not_ subsystems but access is needed
/// due to other limitations.
///
/// For subsystems, use the `_with` variants of the builder.
handle: #handle,
/// The side consumed by the `spawned` side of the orchestra pattern.
consumer: #support_crate ::metered::MeteredReceiver < #event >,
}
impl #connector {
/// Obtain access to the orchestra handle.
pub fn as_handle_mut(&mut self) -> &mut #handle {
&mut self.handle
}
/// Obtain access to the orchestra handle.
pub fn as_handle(&self) -> &#handle {
&self.handle
}
/// Obtain a clone of the handle.
pub fn handle(&self) -> #handle {
self.handle.clone()
}
/// Create a new connector with non-default event channel capacity.
pub fn with_event_capacity(event_channel_capacity: usize) -> Self {
let (events_tx, events_rx) = #support_crate ::metered::channel::<
#event
>(event_channel_capacity);
Self {
handle: events_tx,
consumer: events_rx,
}
}
}
impl ::std::default::Default for #connector {
fn default() -> Self {
Self::with_event_capacity(SIGNAL_CHANNEL_CAPACITY)
}
}
});
ts.extend(quote!{
/// Builder pattern to create compile time safe construction path.
pub struct #builder <InitStateSpawner, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
{
#(
#subsystem_name: #subsystem_passthrough_state_generics,
)*
#(
#baggage_name: #baggage_passthrough_state_generics,
)*
spawner: InitStateSpawner,
// user provided runtime overrides,
// if `None`, the `orchestra(message_capacity=123,..)` is used
// or the default value.
channel_capacity: Option<usize>,
signal_capacity: Option<usize>,
}
});
ts.extend(quote! {
impl<#initialized_builder_generics> #builder<Missing<S>, #( Missing<#field_type>, )*>
{
/// Create a new builder pattern, with all fields being uninitialized.
fn new() -> 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 ::OrchestraError>
{}
trait_from_must_be_implemented::< #error_ty >();
Self {
#(
#field_name: Missing::<#field_type>::default(),
)*
spawner: Missing::<S>::default(),
channel_capacity: None,
signal_capacity: None,
}
}
}
});
// Spawner setter
ts.extend(quote!{
impl<S, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder<Missing<S>, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
#spawner_where_clause,
{
/// The `spawner` to use for spawning tasks.
pub fn spawner(self, spawner: S) -> #builder<
Init<S>,
#( #subsystem_passthrough_state_generics, )*
#( #baggage_passthrough_state_generics, )*
>
{
#builder {
#(
#field_name: self. #field_name,
)*
spawner: Init::<S>::Value(spawner),
channel_capacity: self.channel_capacity,
signal_capacity: self.signal_capacity,
}
}
}
});
// message and signal channel capacity
ts.extend(quote! {
impl<S, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
#builder<Init<S>, #( #subsystem_passthrough_state_generics, )* #( #baggage_passthrough_state_generics, )*>
where
#spawner_where_clause,
{
/// Set the interconnecting signal channel capacity.
pub fn signal_channel_capacity(mut self, capacity: usize) -> Self
{
self.signal_capacity = Some(capacity);
self
}
/// Set the interconnecting message channel capacities.
pub fn message_channel_capacity(mut self, capacity: usize) -> Self
{
self.channel_capacity = Some(capacity);
self
}
}
});
// Create the string literals for spawn.
let subsystem_name_str_literal = subsystem_name
.iter()
.map(|ident| proc_macro2::Literal::string(ident.to_string().replace("_", "-").as_str()))
.collect::<Vec<_>>();
ts.extend(quote! {
/// Type used to represent a builder where all fields are initialized and the orchestra could be constructed.
pub type #initialized_builder<#initialized_builder_generics> = #builder<Init<S>, #( Init<#field_type>, )*>;
// A builder specialization where all fields are set
impl<#initialized_builder_generics> #initialized_builder<#initialized_builder_generics>
where
#spawner_where_clause,
#builder_where_clause
{
/// Complete the construction and create the orchestra type.
pub fn build(self)
-> ::std::result::Result<(#orchestra_name<S, #( #baggage_generic_ty, )*>, #handle), #error_ty> {
let connector = #connector ::with_event_capacity(
self.signal_capacity.unwrap_or(SIGNAL_CHANNEL_CAPACITY)
);
self.build_with_connector(connector)
}
/// Complete the construction and create the orchestra type based on an existing `connector`.
pub fn build_with_connector(self, connector: #connector)
-> ::std::result::Result<(#orchestra_name<S, #( #baggage_generic_ty, )*>, #handle), #error_ty>
{
let #connector {
handle: events_tx,
consumer: events_rx,
} = connector;
let handle = events_tx.clone();
let (to_orchestra_tx, to_orchestra_rx) = #support_crate ::metered::unbounded::<
ToOrchestra
>();
#(
let (#channel_name_tx, #channel_name_rx)
=
#support_crate ::metered::channel::<
MessagePacket< #consumes >
>(
self.channel_capacity.unwrap_or(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 = match self.spawner {
Init::Value(value) => value,
_ => unreachable!("Only ever init spawner as value. qed"),
};
let mut running_subsystems = #support_crate ::FuturesUnordered::<
BoxFuture<'static, ::std::result::Result<(), #error_ty > >
>::new();
#(
let #subsystem_name = match self. #subsystem_name {
Init::Fn(func) => func(handle.clone())?,
Init::Value(val) => val,
};
let unbounded_meter = #channel_name_unbounded_rx.meter().clone();
// Prefer unbounded channel when selecting
let message_rx: SubsystemIncomingMessages< #consumes > = #support_crate ::select_with_strategy(
#channel_name_rx, #channel_name_unbounded_rx,
#support_crate ::select_message_channel_strategy
);
let (signal_tx, signal_rx) = #support_crate ::metered::channel(
self.signal_capacity.unwrap_or(SIGNAL_CHANNEL_CAPACITY)
);
let ctx = #subsystem_ctx_name::< #consumes >::new(
signal_rx,
message_rx,
channels_out.clone(),
to_orchestra_tx.clone(),
#subsystem_name_str_literal
);
let #subsystem_name: OrchestratedSubsystem< #consumes > =
spawn::<_,_, #blocking, _, _, _>(
&mut spawner,
#channel_name_tx,
signal_tx,
unbounded_meter,
ctx,
#subsystem_name,
#subsystem_name_str_literal,
&mut running_subsystems,
)?;
)*
use #support_crate ::StreamExt;
let to_orchestra_rx = to_orchestra_rx.fuse();
let orchestra = #orchestra_name {
#(
#subsystem_name,
)*
#(
#baggage_name: match self. #baggage_name {
Init::Value(val) => val,
_ => panic!("unexpected baggage initialization, must be value"),
},
)*
spawner,
running_subsystems,
events_rx,
to_orchestra_rx,
};
Ok((orchestra, handle))
}
}
});
ts.extend(baggage_specific_setters);
ts.extend(subsystem_specific_setters);
ts.extend(impl_task_kind(info));
ts
}
pub(crate) fn impl_task_kind(info: &OrchestraInfo) -> 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! {
/// 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: Spawner>(spawner: &mut S, task_name: &'static str, subsystem_name: &'static str, future: BoxFuture<'static, ()>);
}
#[allow(missing_docs)]
struct Regular;
impl TaskKind for Regular {
fn launch_task<S: Spawner>(spawner: &mut S, task_name: &'static str, subsystem_name: &'static str, future: BoxFuture<'static, ()>) {
spawner.spawn(task_name, Some(subsystem_name), future)
}
}
#[allow(missing_docs)]
struct Blocking;
impl TaskKind for Blocking {
fn launch_task<S: Spawner>(spawner: &mut S, task_name: &'static str, subsystem_name: &'static str, future: BoxFuture<'static, ()>) {
spawner.spawn_blocking(task_name, Some(subsystem_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,
ctx: Ctx,
s: SubSys,
subsystem_name: &'static str,
futures: &mut #support_crate ::FuturesUnordered<BoxFuture<'static, ::std::result::Result<(), #error_ty> >>,
) -> ::std::result::Result<OrchestratedSubsystem<M>, #error_ty >
where
S: #support_crate ::Spawner,
M: std::fmt::Debug + Send + 'static,
TK: TaskKind,
Ctx: #support_crate ::SubsystemContext<Message=M>,
E: ::std::error::Error + Send + Sync + 'static + ::std::convert::From<#support_crate ::OrchestraError>,
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, subsystem_name, fut);
futures.push(Box::pin(
rx.map(|e| {
#support_crate ::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(OrchestratedSubsystem {
instance,
})
}
};
ts
}
@@ -1,142 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use quote::quote;
use syn::Result;
use super::*;
/// Implement the helper type `ChannelsOut` and `MessagePacket<T>`.
pub(crate) fn impl_channels_out_struct(info: &OrchestraInfo) -> 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 orchestra.
#[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 >
>,
)*
}
#[allow(unreachable_code)]
// when no defined messages in enum
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(()),
#[allow(unreachable_patterns)]
// And everything that's not WIP but no subsystem consumes it
unused_msg => {
#support_crate :: tracing :: warn!("Nothing consumes {:?}", unused_msg);
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,
) {
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(()),
// And everything that's not WIP but no subsystem consumes it
#[allow(unreachable_patterns)]
unused_msg => {
#support_crate :: tracing :: warn!("Nothing consumes {:?}", unused_msg);
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)
}
@@ -1,85 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use quote::quote;
use syn::{spanned::Spanned, Result};
use super::*;
/// Generates the wrapper type enum.
pub(crate) fn impl_message_wrapper_enum(info: &OrchestraInfo) -> Result<proc_macro2::TokenStream> {
let consumes = info.any_message();
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 over all possible messages
/// used by any subsystem.
#[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)
}
@@ -1,265 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use quote::quote;
use super::*;
pub(crate) fn impl_orchestra_struct(info: &OrchestraInfo) -> proc_macro2::TokenStream {
let message_wrapper = &info.message_wrapper.clone();
let orchestra_name = info.orchestra_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 ::Spawner,
};
// 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(orchestra_name.to_string().to_lowercase().as_str(), orchestra_name.span());
let ts = quote! {
/// Capacity of a bounded message channel between orchestra 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 orchestra.
const SIGNAL_CHANNEL_CAPACITY: usize = #signal_channel_capacity;
/// The log target tag.
const LOG_TARGET: &'static str = #log_target;
/// The orchestra.
pub struct #orchestra_name #generics {
#(
/// A subsystem instance.
#subsystem_name: OrchestratedSubsystem< #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_orchestra_rx: #support_crate ::stream::Fuse<
#support_crate ::metered::UnboundedMeteredReceiver< #support_crate ::ToOrchestra >
>,
/// Events that are sent to the orchestra from the outside world.
events_rx: #support_crate ::metered::MeteredReceiver< #event_ty >,
}
impl #generics #orchestra_name #generics #where_clause {
/// Send the given signal, a termination 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 {
#support_crate ::futures::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 ) =>
OrchestratedSubsystem::< #consumes >::send_message2(&mut self. #subsystem_name, inner, origin ).await?,
)*
// subsystems that are still work in progress
#(
#message_wrapper :: #unconsumes_variant ( _ ) => {}
)*
#message_wrapper :: Empty => {}
// And everything that's not WIP but no subsystem consumes it
#[allow(unreachable_patterns)]
unused_msg => {
#support_crate :: tracing :: warn!("Nothing consumes {:?}", unused_msg);
}
}
Ok(())
}
/// Extract information from each subsystem.
pub fn map_subsystems<'a, Mapper, Output>(&'a self, mapper: Mapper)
-> Vec<Output>
where
#(
Mapper: MapSubsystem<&'a OrchestratedSubsystem< #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_orchestrated_subsystem(info: &OrchestraInfo) -> 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! {
/// A subsystem that the orchestrator orchestrates.
///
/// 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 OrchestratedSubsystem<M> {
/// The instance.
pub instance: std::option::Option<
#support_crate ::SubsystemInstance<M, #signal>
>,
}
impl<M> OrchestratedSubsystem<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 ::OrchestraError::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 ::OrchestraError::SubsystemStalled(instance.name)
))
}
Some(res) => {
let res = res.map_err(Into::into);
if res.is_ok() {
instance.signals_received += 1;
}
res
}
}
} else {
Ok(())
}
}
}
};
ts
}
@@ -1,654 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Ident, Path, Result, Type};
use petgraph::{visit::EdgeRef, Direction};
use super::*;
/// Generates all subsystem types and related accumulation traits.
pub(crate) fn impl_subsystem_types_all(info: &OrchestraInfo) -> Result<TokenStream> {
let mut ts = TokenStream::new();
let orchestra_name = &info.orchestra_name;
let span = orchestra_name.span();
let all_messages_wrapper = &info.message_wrapper;
let support_crate = info.support_crate_name();
let signal_ty = &info.extern_signal_ty;
let error_ty = &info.extern_error_ty;
let cg = graph::ConnectionGraph::construct(info.subsystems());
let graph = &cg.graph;
// All outgoing edges are now usable to derive everything we need
for node_index in graph.node_indices() {
let subsystem_name = graph[node_index].to_string();
let outgoing_wrapper = Ident::new(&(subsystem_name + "OutgoingMessages"), span);
// cannot be a hashmap, duplicate keys and sorting required
// maps outgoing messages to the subsystem that consumes it
let outgoing_to_consumer = graph
.edges_directed(node_index, Direction::Outgoing)
.map(|edge| {
let message_ty = edge.weight();
let subsystem_generic_consumer = graph[edge.target()].clone();
Ok((to_variant(message_ty, span.clone())?, subsystem_generic_consumer))
})
.collect::<Result<Vec<(Ident, Ident)>>>()?;
// Split it for usage with quote
let outgoing_variant = outgoing_to_consumer.iter().map(|x| x.0.clone()).collect::<Vec<_>>();
let subsystem_generic = outgoing_to_consumer.into_iter().map(|x| x.1).collect::<Vec<_>>();
ts.extend(quote! {
impl ::std::convert::From< #outgoing_wrapper > for #all_messages_wrapper {
fn from(message: #outgoing_wrapper) -> Self {
match message {
#(
#outgoing_wrapper :: #outgoing_variant ( msg ) => #all_messages_wrapper :: #subsystem_generic ( msg ),
)*
#outgoing_wrapper :: Empty => #all_messages_wrapper :: Empty,
// And everything that's not WIP but no subsystem consumes it
#[allow(unreachable_patterns)]
unused_msg => {
#support_crate :: tracing :: warn!("Nothing consumes {:?}", unused_msg);
#all_messages_wrapper :: Empty
}
}
}
}
})
}
// Dump the graph to file.
#[cfg(feature = "graph")]
{
let path = std::path::PathBuf::from(env!("OUT_DIR"))
.join(orchestra_name.to_string().to_lowercase() + "-subsystem-messaging.dot");
if let Err(e) = std::fs::OpenOptions::new()
.truncate(true)
.create(true)
.write(true)
.open(&path)
.and_then(|mut f| cg.graphviz(&mut f))
{
eprintln!("Failed to write dot graph to {}: {:?}", path.display(), e);
} else {
println!("Wrote dot graph to {}", path.display());
}
}
let subsystem_sender_name = &Ident::new(&(orchestra_name.to_string() + "Sender"), span);
let subsystem_ctx_name = &Ident::new(&(orchestra_name.to_string() + "SubsystemContext"), span);
ts.extend(impl_subsystem_context(info, &subsystem_sender_name, &subsystem_ctx_name));
ts.extend(impl_associate_outgoing_messages_trait(&all_messages_wrapper));
ts.extend(impl_subsystem_sender(
support_crate,
info.subsystems().iter().map(|ssf| {
let outgoing_wrapper =
Ident::new(&(ssf.generic.to_string() + "OutgoingMessages"), span);
outgoing_wrapper
}),
&all_messages_wrapper,
&subsystem_sender_name,
));
// Create all subsystem specific types, one by one
for ssf in info.subsystems() {
let subsystem_name = ssf.generic.to_string();
let outgoing_wrapper = &Ident::new(&(subsystem_name.clone() + "OutgoingMessages"), span);
let subsystem_ctx_trait = &Ident::new(&(subsystem_name.clone() + "ContextTrait"), span);
let subsystem_sender_trait = &Ident::new(&(subsystem_name.clone() + "SenderTrait"), span);
ts.extend(impl_per_subsystem_helper_traits(
info,
subsystem_ctx_name,
subsystem_ctx_trait,
subsystem_sender_name,
subsystem_sender_trait,
&ssf.message_to_consume,
&ssf.messages_to_send,
outgoing_wrapper,
));
ts.extend(impl_associate_outgoing_messages(&ssf.message_to_consume, &outgoing_wrapper));
ts.extend(impl_wrapper_enum(&outgoing_wrapper, ssf.messages_to_send.as_slice())?);
}
// impl the emtpy tuple handling for tests
let empty_tuple: Type = parse_quote! { () };
ts.extend(impl_subsystem_context_trait_for(
empty_tuple.clone(),
&[],
empty_tuple,
all_messages_wrapper,
subsystem_ctx_name,
subsystem_sender_name,
support_crate,
signal_ty,
error_ty,
));
Ok(ts)
}
/// Extract the final component of the message type path as used in the `#[subsystem(consumes: path::to::Foo)]` annotation.
fn to_variant(path: &Path, span: Span) -> Result<Ident> {
let ident = path
.segments
.last()
.ok_or_else(|| syn::Error::new(span, "Path is empty, but it must end with an identifier"))
.map(|segment| segment.ident.clone())?;
Ok(ident)
}
/// Converts the outgoing message types to variants.
///
/// Note: Commonly this is `${X}Message` becomes `${X}OutgoingMessages::${X}Message`
/// where for `AllMessages` it would be `AllMessages::${X}`.
fn to_variants(message_types: &[Path], span: Span) -> Result<Vec<Ident>> {
let variants: Vec<_> =
Result::from_iter(message_types.into_iter().map(|path| to_variant(path, span.clone())))?;
Ok(variants)
}
/// Generates the wrapper type enum, no bells or whistles.
pub(crate) fn impl_wrapper_enum(wrapper: &Ident, message_types: &[Path]) -> Result<TokenStream> {
// The message types are path based, each of them must finish with a type
// and as such we do this upfront.
let variants = to_variants(message_types, wrapper.span())?;
let ts = quote! {
#[allow(missing_docs)]
#[derive(Debug)]
pub enum #wrapper {
#(
#variants ( #message_types ),
)*
Empty,
}
#(
impl ::std::convert::From< #message_types > for #wrapper {
fn from(message: #message_types) -> Self {
#wrapper :: #variants ( message )
}
}
)*
// Useful for unit and integration tests:
impl ::std::convert::From< () > for #wrapper {
fn from(_message: ()) -> Self {
#wrapper :: Empty
}
}
};
Ok(ts)
}
/// Create the subsystem sender type and implements `trait SubsystemSender`
/// for the `#outgoing_wrappers: From<OutgoingMessage>` with the proper associated types.
pub(crate) fn impl_subsystem_sender(
support_crate: &Path,
outgoing_wrappers: impl IntoIterator<Item = Ident>,
all_messages_wrapper: &Ident,
subsystem_sender_name: &Ident,
) -> TokenStream {
let mut ts = quote! {
/// Connector to send messages towards all subsystems,
/// while tracking the which signals where already received.
#[derive(Debug)]
pub struct #subsystem_sender_name < OutgoingWrapper > {
/// Collection of channels to all subsystems.
channels: ChannelsOut,
/// Systemwide tick for which signals were received by all subsystems.
signals_received: SignalsReceived,
/// Keep that marker around.
_phantom: ::core::marker::PhantomData< OutgoingWrapper >,
}
// can't derive due to `PhantomData` and `OutgoingWrapper` not being
// bounded by `Clone`.
impl<OutgoingWrapper> std::clone::Clone for #subsystem_sender_name < OutgoingWrapper > {
fn clone(&self) -> Self {
Self {
channels: self.channels.clone(),
signals_received: self.signals_received.clone(),
_phantom: ::core::marker::PhantomData::default(),
}
}
}
};
// Create the same for a wrapping enum:
//
// 1. subsystem specific `*OutgoingMessages`-type
// 2. orchestra-global-`AllMessages`-type
let wrapped = |outgoing_wrapper: &TokenStream| {
quote! {
#[#support_crate ::async_trait]
impl<OutgoingMessage> SubsystemSender< OutgoingMessage > for #subsystem_sender_name < #outgoing_wrapper >
where
OutgoingMessage: Send + 'static,
#outgoing_wrapper: ::std::convert::From<OutgoingMessage> + Send,
#all_messages_wrapper: ::std::convert::From< #outgoing_wrapper > + Send,
{
async fn send_message(&mut self, msg: OutgoingMessage)
{
self.channels.send_and_log_error(
self.signals_received.load(),
<#all_messages_wrapper as ::std::convert::From<_>> ::from (
<#outgoing_wrapper as ::std::convert::From<_>> :: from ( msg )
)
).await;
}
async fn send_messages<I>(&mut self, msgs: I)
where
I: IntoIterator<Item=OutgoingMessage> + Send,
I::IntoIter: Iterator<Item=OutgoingMessage> + Send,
{
for msg in msgs {
self.send_message( msg ).await;
}
}
fn send_unbounded_message(&mut self, msg: OutgoingMessage)
{
self.channels.send_unbounded_and_log_error(
self.signals_received.load(),
<#all_messages_wrapper as ::std::convert::From<_>> ::from (
<#outgoing_wrapper as ::std::convert::From<_>> :: from ( msg )
)
);
}
}
}
};
for outgoing_wrapper in outgoing_wrappers {
ts.extend(wrapped(&quote! {
#outgoing_wrapper
}));
}
ts.extend(wrapped(&quote! {
()
}));
ts
}
/// Define the `trait AssociateOutgoing` and implement it for `#all_messages_wrapper` and `()`.
pub(crate) fn impl_associate_outgoing_messages_trait(all_messages_wrapper: &Ident) -> TokenStream {
quote! {
/// Binds a generated type covering all declared outgoing messages,
/// which implements `#generated_outgoing: From<M>` for all annotated types
/// of a particular subsystem.
///
/// Note: This works because there is a 1?:1 relation between consumed messages and subsystems.
pub trait AssociateOutgoing: ::std::fmt::Debug + Send {
/// The associated _outgoing_ messages for a subsystem that _consumes_ the message `Self`.
type OutgoingMessages: Into< #all_messages_wrapper > + ::std::fmt::Debug + Send;
}
// Helper for tests, where nothing is ever sent.
impl AssociateOutgoing for () {
type OutgoingMessages = ();
}
// Helper for tests, allows sending of arbitrary messages give
// an test context.
impl AssociateOutgoing for #all_messages_wrapper {
type OutgoingMessages = #all_messages_wrapper ;
}
}
}
/// Implement `AssociateOutgoing` for `#consumes` being handled by a particular subsystem.
///
/// Binds the outgoing messages to the inbound message.
///
/// Note: Works, since there is a 1:1 relation between inbound message type and subsystem declarations.
/// Note: A workaround until default associated types work in `rustc`.
pub(crate) fn impl_associate_outgoing_messages(
consumes: &Path,
outgoing_wrapper: &Ident,
) -> TokenStream {
quote! {
impl AssociateOutgoing for #outgoing_wrapper {
type OutgoingMessages = #outgoing_wrapper;
}
impl AssociateOutgoing for #consumes {
type OutgoingMessages = #outgoing_wrapper;
}
}
}
/// Implement `trait SubsystemContext` for a particular subsystem context,
/// that is generated by the proc-macro too.
pub(crate) fn impl_subsystem_context_trait_for(
consumes: Type,
outgoing: &[Type],
outgoing_wrapper: Type,
all_messages_wrapper: &Ident,
subsystem_ctx_name: &Ident,
subsystem_sender_name: &Ident,
support_crate: &Path,
signal: &Path,
error_ty: &Path,
) -> TokenStream {
// impl the subsystem context trait
let where_clause = quote! {
#consumes: AssociateOutgoing + ::std::fmt::Debug + Send + 'static,
#all_messages_wrapper: From< #outgoing_wrapper >,
#all_messages_wrapper: From< #consumes >,
#outgoing_wrapper: #( From< #outgoing > )+*,
};
quote! {
#[#support_crate ::async_trait]
impl #support_crate ::SubsystemContext for #subsystem_ctx_name < #consumes >
where
#where_clause
{
type Message = #consumes;
type Signal = #signal;
type OutgoingMessages = #outgoing_wrapper;
type Sender = #subsystem_sender_name < #outgoing_wrapper >;
type Error = #error_ty;
async fn try_recv(&mut self) -> ::std::result::Result<Option<FromOrchestra< Self::Message, #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<FromOrchestra<Self::Message, #signal>, #error_ty> {
loop {
// If we have a message pending an orchestra 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 ::FromOrchestra::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 ::OrchestraError::Context(
"Signal channel is terminated and empty."
.to_owned()
))?;
self.signals_received.inc();
return Ok( #support_crate ::FromOrchestra::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_orchestra = #support_crate ::futures::select_biased! {
signal = await_signal => {
let signal = signal
.ok_or( #support_crate ::OrchestraError::Context(
"Signal channel is terminated and empty."
.to_owned()
))?;
#support_crate ::FromOrchestra::Signal(signal)
}
msg = await_message => {
let packet = msg
.ok_or( #support_crate ::OrchestraError::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 ::FromOrchestra::Communication { msg: packet.message}
}
}
};
if let #support_crate ::FromOrchestra::Signal(_) = from_orchestra {
self.signals_received.inc();
}
return Ok(from_orchestra);
}
}
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_orchestra.unbounded_send(#support_crate ::ToOrchestra::SpawnJob {
name,
subsystem: Some(self.name()),
s,
}).map_err(|_| #support_crate ::OrchestraError::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_orchestra.unbounded_send(#support_crate ::ToOrchestra::SpawnBlockingJob {
name,
subsystem: Some(self.name()),
s,
}).map_err(|_| #support_crate ::OrchestraError::TaskSpawn(name))?;
Ok(())
}
}
}
}
/// Implement the additional subsystem accumulation traits, for simplified usage,
/// i.e. `${Subsystem}SenderTrait` and `${Subsystem}ContextTrait`.
pub(crate) fn impl_per_subsystem_helper_traits(
info: &OrchestraInfo,
subsystem_ctx_name: &Ident,
subsystem_ctx_trait: &Ident,
subsystem_sender_name: &Ident,
subsystem_sender_trait: &Ident,
consumes: &Path,
outgoing: &[Path],
outgoing_wrapper: &Ident,
) -> TokenStream {
let all_messages_wrapper = &info.message_wrapper;
let signal_ty = &info.extern_signal_ty;
let error_ty = &info.extern_error_ty;
let support_crate = info.support_crate_name();
let mut ts = TokenStream::new();
// Create a helper trait bound of all outgoing messages, and the generated wrapper type
// for ease of use within subsystems:
let acc_sender_trait_bounds = quote! {
#support_crate ::SubsystemSender< #outgoing_wrapper >
#(
+ #support_crate ::SubsystemSender< #outgoing >
)*
+ #support_crate ::SubsystemSender< () >
+ Send
+ 'static
};
ts.extend(quote! {
/// A abstracting trait for usage with subsystems.
pub trait #subsystem_sender_trait : #acc_sender_trait_bounds
{}
impl<T> #subsystem_sender_trait for T
where
T: #acc_sender_trait_bounds
{}
});
// Create a helper accumulated per subsystem trait bound:
let where_clause = quote! {
#consumes: AssociateOutgoing + ::std::fmt::Debug + Send + 'static,
#all_messages_wrapper: From< #outgoing_wrapper >,
#all_messages_wrapper: From< #consumes >,
#all_messages_wrapper: From< () >,
#outgoing_wrapper: #( From< #outgoing > )+*,
#outgoing_wrapper: From< () >,
};
ts.extend(quote! {
/// Accumulative trait for a particular subsystem wrapper.
pub trait #subsystem_ctx_trait : SubsystemContext <
Message = #consumes,
Signal = #signal_ty,
OutgoingMessages = #outgoing_wrapper,
// Sender,
Error = #error_ty,
>
where
#where_clause
<Self as SubsystemContext>::Sender:
#subsystem_sender_trait
+ #acc_sender_trait_bounds,
{
/// Sender.
type Sender: #subsystem_sender_trait;
}
impl<T> #subsystem_ctx_trait for T
where
T: SubsystemContext <
Message = #consumes,
Signal = #signal_ty,
OutgoingMessages = #outgoing_wrapper,
// Sender
Error = #error_ty,
>,
#where_clause
<T as SubsystemContext>::Sender:
#subsystem_sender_trait
+ #acc_sender_trait_bounds,
{
type Sender = <T as SubsystemContext>::Sender;
}
});
ts.extend(impl_subsystem_context_trait_for(
parse_quote! { #consumes },
&Vec::from_iter(outgoing.iter().map(|path| {
parse_quote! { #path }
})),
parse_quote! { #outgoing_wrapper },
all_messages_wrapper,
subsystem_ctx_name,
subsystem_sender_name,
support_crate,
signal_ty,
error_ty,
));
ts
}
/// Generate the subsystem context type and provide `fn new` on it.
///
/// Note: The generated `fn new` is used by the [builder pattern](../impl_builder.rs).
pub(crate) fn impl_subsystem_context(
info: &OrchestraInfo,
subsystem_sender_name: &Ident,
subsystem_ctx_name: &Ident,
) -> TokenStream {
let signal_ty = &info.extern_signal_ty;
let support_crate = info.support_crate_name();
let ts = quote! {
/// 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.
///
/// [`Orchestra`]: struct.Orchestra.html
/// [`Subsystem`]: trait.Subsystem.html
/// [`SubsystemJob`]: trait.SubsystemJob.html
#[derive(Debug)]
#[allow(missing_docs)]
pub struct #subsystem_ctx_name<M: AssociateOutgoing + Send + 'static> {
signals: #support_crate ::metered::MeteredReceiver< #signal_ty >,
messages: SubsystemIncomingMessages< M >,
to_subsystems: #subsystem_sender_name < <M as AssociateOutgoing>::OutgoingMessages >,
to_orchestra: #support_crate ::metered::UnboundedMeteredSender<
#support_crate ::ToOrchestra
>,
signals_received: SignalsReceived,
pending_incoming: Option<(usize, M)>,
name: &'static str
}
impl<M> #subsystem_ctx_name <M>
where
M: AssociateOutgoing + Send + 'static,
{
/// Create a new context.
fn new(
signals: #support_crate ::metered::MeteredReceiver< #signal_ty >,
messages: SubsystemIncomingMessages< M >,
to_subsystems: ChannelsOut,
to_orchestra: #support_crate ::metered::UnboundedMeteredSender<#support_crate:: ToOrchestra>,
name: &'static str
) -> Self {
let signals_received = SignalsReceived::default();
#subsystem_ctx_name :: <M> {
signals,
messages,
to_subsystems: #subsystem_sender_name :: < <M as AssociateOutgoing>::OutgoingMessages > {
channels: to_subsystems,
signals_received: signals_received.clone(),
_phantom: ::core::marker::PhantomData::default(),
},
to_orchestra,
signals_received,
pending_incoming: None,
name
}
}
fn name(&self) -> &'static str {
self.name
}
}
};
ts
}
@@ -1,89 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::{Ident, Span, TokenStream};
use syn::{parse_quote, spanned::Spanned, Path};
mod graph;
mod impl_builder;
mod impl_channels_out;
mod impl_message_wrapper;
mod impl_orchestra;
mod impl_subsystem_ctx_sender;
mod orchestra;
mod parse;
mod subsystem;
#[cfg(test)]
mod tests;
use impl_builder::*;
use impl_channels_out::*;
use impl_message_wrapper::*;
use impl_orchestra::*;
use impl_subsystem_ctx_sender::*;
use parse::*;
use self::{orchestra::*, subsystem::*};
/// Obtain the support crate `Path` as `TokenStream`.
pub(crate) fn support_crate() -> Result<Path, proc_macro_crate::Error> {
Ok(if cfg!(test) {
parse_quote! {crate}
} else {
use proc_macro_crate::{crate_name, FoundCrate};
let crate_name = crate_name("orchestra")?;
match crate_name {
FoundCrate::Itself => parse_quote! {crate},
FoundCrate::Name(name) => Ident::new(&name, Span::call_site()).into(),
}
})
}
#[proc_macro_attribute]
pub fn orchestra(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
impl_orchestra_gen(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
#[proc_macro_attribute]
pub fn subsystem(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
impl_subsystem_context_trait_bounds(attr, item, MakeSubsystem::ImplSubsystemTrait)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
#[proc_macro_attribute]
pub fn contextbounds(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let attr: TokenStream = attr.into();
let item: TokenStream = item.into();
impl_subsystem_context_trait_bounds(attr, item, MakeSubsystem::AddContextTraitBounds)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
@@ -1,66 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use proc_macro2::TokenStream;
use syn::{parse2, Result};
use super::{parse::*, *};
pub(crate) fn impl_orchestra_gen(
attr: TokenStream,
orig: TokenStream,
) -> Result<proc_macro2::TokenStream> {
let args: OrchestraAttrArgs = parse2(attr)?;
let message_wrapper = args.message_wrapper;
let of: OrchestraGuts = parse2(orig)?;
let support_crate = support_crate().expect("The crate this macro is run for, includes the proc-macro support as dependency, otherwise it could not be run in the first place. qed");
let info = OrchestraInfo {
support_crate,
subsystems: of.subsystems,
baggage: of.baggage,
orchestra_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,
outgoing_ty: args.outgoing_ty,
};
let mut additive = impl_orchestra_struct(&info);
additive.extend(impl_builder(&info));
additive.extend(impl_orchestrated_subsystem(&info));
additive.extend(impl_channels_out_struct(&info));
additive.extend(impl_subsystem_types_all(&info)?);
additive.extend(impl_message_wrapper_enum(&info)?);
let ts = expander::Expander::new("orchestra-expansion")
.add_comment("Generated orchestra code by `#[orchestra(..)]`".to_owned())
.dry(!cfg!(feature = "expand"))
.verbose(true)
// once all our needed format options are available on stable
// we should enabled this again, until then too many warnings
// are generated
// .fmt(expander::Edition::_2021)
.write_to_out_dir(additive)
.expect("Expander does not fail due to IO in OUT_DIR. qed");
Ok(ts)
}
@@ -1,38 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
mod kw {
syn::custom_keyword!(event);
syn::custom_keyword!(signal);
syn::custom_keyword!(error);
syn::custom_keyword!(outgoing);
syn::custom_keyword!(gen);
syn::custom_keyword!(signal_capacity);
syn::custom_keyword!(message_capacity);
syn::custom_keyword!(subsystem);
syn::custom_keyword!(prefix);
}
mod parse_orchestra_attr;
mod parse_orchestra_struct;
mod parse_subsystem_attr;
#[cfg(test)]
mod tests;
pub(crate) use self::{parse_orchestra_attr::*, parse_orchestra_struct::*};
pub(crate) use self::parse_subsystem_attr::*;
@@ -1,190 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::kw;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use std::collections::{hash_map::RandomState, HashMap};
use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
spanned::Spanned,
Error, Ident, LitInt, Path, Result, Token,
};
#[derive(Clone, Debug)]
enum OrchestraAttrItem {
ExternEventType { tag: kw::event, eq_token: Token![=], value: Path },
ExternOrchestraSignalType { 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 OrchestraAttrItem {
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::ExternOrchestraSignalType { 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 OrchestraAttrItem {
fn parse(input: &ParseBuffer) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::event) {
Ok(OrchestraAttrItem::ExternEventType {
tag: input.parse::<kw::event>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::signal) {
Ok(OrchestraAttrItem::ExternOrchestraSignalType {
tag: input.parse::<kw::signal>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::error) {
Ok(OrchestraAttrItem::ExternErrorType {
tag: input.parse::<kw::error>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::outgoing) {
Ok(OrchestraAttrItem::OutgoingType {
tag: input.parse::<kw::outgoing>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::gen) {
Ok(OrchestraAttrItem::MessageWrapperName {
tag: input.parse::<kw::gen>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::signal_capacity) {
Ok(OrchestraAttrItem::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(OrchestraAttrItem::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 OrchestraAttrArgs {
pub(crate) message_wrapper: Ident,
pub(crate) extern_event_ty: Path,
pub(crate) extern_signal_ty: Path,
pub(crate) extern_error_ty: 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 OrchestraAttrItem::$variant { value, .. } = item {
Some(value.clone())
} else {
None
}
})
};
}
impl Parse for OrchestraAttrArgs {
fn parse(input: &ParseBuffer) -> Result<Self> {
let items: Punctuated<OrchestraAttrItem, Token![,]> =
input.parse_terminated(OrchestraAttrItem::parse)?;
let mut unique = HashMap::<
std::mem::Discriminant<OrchestraAttrItem>,
OrchestraAttrItem,
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 orchestra 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 orchestra error type via `error=..`.")?;
let event = extract_variant!(unique, ExternEventType; err = "Must declare the orchestra event type via `event=..`.")?;
let signal = extract_variant!(unique, ExternOrchestraSignalType; err = "Must declare the orchestra signal type via `signal=..`.")?;
let message_wrapper = extract_variant!(unique, MessageWrapperName; err = "Must declare the orchestra generated wrapping message type via `gen=..`.")?;
let outgoing = extract_variant!(unique, OutgoingType);
Ok(OrchestraAttrArgs {
signal_channel_capacity,
message_channel_capacity,
extern_event_ty: event,
extern_signal_ty: signal,
extern_error_ty: error,
outgoing_ty: outgoing,
message_wrapper,
})
}
}
@@ -1,601 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use itertools::Itertools;
use proc_macro2::{Span, TokenStream};
use std::collections::{hash_map::RandomState, HashMap, HashSet};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
token::Bracket,
AttrStyle, Error, Field, FieldsNamed, GenericParam, Ident, ItemStruct, Path, PathSegment,
Result, Token, Type, Visibility,
};
use quote::{quote, ToTokens};
mod kw {
syn::custom_keyword!(wip);
syn::custom_keyword!(blocking);
syn::custom_keyword!(consumes);
syn::custom_keyword!(sends);
}
#[derive(Clone, Debug)]
pub(crate) 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),
/// Message to be sent by this subsystem.
Sends(Sends),
/// Message to be consumed by this subsystem.
Consumes(Consumes),
}
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::sends) {
Self::Sends(input.parse::<Sends>()?)
} else {
Self::Consumes(input.parse::<Consumes>()?)
})
}
}
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::Sends(_) => {
quote! {}
},
Self::Consumes(_) => {
quote! {}
},
};
tokens.extend(ts.into_iter());
}
}
/// A field of the struct annotated with
/// `#[subsystem(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 of message to be consumed by the subsystem.
pub(crate) message_to_consume: Path,
/// Types of messages to be sent by the subsystem.
pub(crate) messages_to_send: Vec<Path>,
/// 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 instantiated with the builder pattern.
pub(crate) wip: bool,
}
// Converts a type enum to a path if this type is a TypePath
fn try_type_to_path(ty: &Type, span: Span) -> Result<Path> {
match ty {
Type::Path(path) => Ok(path.path.clone()),
_ => Err(Error::new(span, "Type must be a path expression.")),
}
}
// Converts a Rust type to a list of idents recursively checking the possible values
fn flatten_type(ty: &Type, span: Span) -> Result<Vec<Ident>> {
match ty {
syn::Type::Array(ar) => flatten_type(&ar.elem, span),
syn::Type::Paren(par) => flatten_type(&par.elem, span),
syn::Type::Path(type_path) => type_path
.path
.segments
.iter()
.map(|seg| flatten_path_segments(seg, span.clone()))
.flatten_ok()
.collect::<Result<Vec<_>>>(),
syn::Type::Tuple(tup) => tup
.elems
.iter()
.map(|element| flatten_type(element, span.clone()))
.flatten_ok()
.collect::<Result<Vec<_>>>(),
_ => Err(Error::new(span, format!("Unsupported type: {:?}", ty))),
}
}
// Flatten segments of some path to a list of idents used in these segments
fn flatten_path_segments(path_segment: &PathSegment, span: Span) -> Result<Vec<Ident>> {
let mut result = vec![path_segment.ident.clone()];
match &path_segment.arguments {
syn::PathArguments::AngleBracketed(args) => {
let mut recursive_idents = args
.args
.iter()
.map(|generic_argument| match generic_argument {
syn::GenericArgument::Type(ty) => flatten_type(ty, span.clone()),
_ => Err(Error::new(
span,
format!(
"Field has a generic with an unsupported parameter {:?}",
generic_argument
),
)),
})
.flatten_ok()
.collect::<Result<Vec<_>>>()?;
result.append(&mut recursive_idents);
},
syn::PathArguments::None => {},
_ =>
return Err(Error::new(
span,
format!(
"Field has a generic with an unsupported path {:?}",
path_segment.arguments
),
)),
}
Ok(result)
}
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 take) => {
$unique.values().find_map(|item| {
if let SubSysAttrItem::$variant(value) = item {
Some(value.clone())
} else {
None
}
})
};
($unique:expr, $variant:ident) => {
$unique.values().find_map(|item| {
if let SubSysAttrItem::$variant(_) = item {
Some(true)
} else {
None
}
})
};
}
#[derive(Debug, Clone)]
pub(crate) struct Sends {
#[allow(dead_code)]
pub(crate) keyword_sends: kw::sends,
#[allow(dead_code)]
pub(crate) colon: Token![:],
#[allow(dead_code)]
pub(crate) bracket: Option<Bracket>,
pub(crate) sends: Punctuated<Path, Token![,]>,
}
impl Parse for Sends {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let content;
let keyword_sends = input.parse()?;
let colon = input.parse()?;
let (bracket, sends) = if !input.peek(syn::token::Bracket) {
let mut sends = Punctuated::new();
sends.push_value(input.parse::<Path>()?);
(None, sends)
} else {
let bracket = Some(syn::bracketed!(content in input));
let sends = Punctuated::parse_terminated(&content)?;
(bracket, sends)
};
Ok(Self { keyword_sends, colon, bracket, sends })
}
}
#[derive(Debug, Clone)]
pub(crate) struct Consumes {
#[allow(dead_code)]
pub(crate) keyword_consumes: Option<kw::consumes>,
#[allow(dead_code)]
pub(crate) colon: Option<Token![:]>,
pub(crate) consumes: Path,
}
impl Parse for Consumes {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let lookahead = input.lookahead1();
Ok(if lookahead.peek(kw::consumes) {
Self {
keyword_consumes: Some(input.parse()?),
colon: input.parse()?,
consumes: input.parse()?,
}
} else {
Self { keyword_consumes: None, colon: None, consumes: input.parse()? }
})
}
}
/// Parses `(Foo, sends = [Bar, Baz])`
/// including the `(` and `)`.
#[derive(Debug, Clone)]
pub(crate) struct SubSystemAttrItems {
/// The subsystem is in progress, 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,
/// If there are blocking components in the subsystem and hence it should be
/// spawned on a dedicated thread pool for such subssytems.
pub(crate) blocking: bool,
/// The message type being consumed by the subsystem.
pub(crate) consumes: Option<Consumes>,
pub(crate) sends: Option<Sends>,
}
impl Parse for SubSystemAttrItems {
fn parse(input: syn::parse::ParseStream) -> Result<Self> {
let span = input.span();
let content;
let _paren_token = parenthesized!(content in input);
let items = content.call(Punctuated::<SubSysAttrItem, Token![,]>::parse_terminated)?;
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(), "Duplicate definition of subsystem attribute found");
e.combine(Error::new(first.span(), "previously defined here."));
return Err(e)
}
}
// A subsystem makes no sense if not one of them is provided
let sends = extract_variant!(unique, Sends take);
let consumes = extract_variant!(unique, Consumes take);
if sends.as_ref().map(|sends| sends.sends.is_empty()).unwrap_or(true) && consumes.is_none()
{
return Err(Error::new(
span,
"Must have at least one of `consumes: [..]` and `sends: [..]`.",
))
}
let blocking = extract_variant!(unique, Blocking; default = false);
let wip = extract_variant!(unique, Wip; default = false);
Ok(Self { blocking, wip, sends, consumes })
}
}
/// Fields that are _not_ subsystems.
#[derive(Debug, Clone)]
pub(crate) struct BaggageField {
pub(crate) field_name: Ident,
pub(crate) field_ty: Type,
pub(crate) generic_types: Vec<Ident>,
pub(crate) vis: Visibility,
}
#[derive(Clone, Debug)]
pub(crate) struct OrchestraInfo {
/// Where the support crate `::orchestra` lives.
pub(crate) support_crate: Path,
/// 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 orchestra struct, used as a prefix for
/// almost all generated types.
pub(crate) orchestra_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,
/// 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 OrchestraInfo {
pub(crate) fn support_crate_name(&self) -> &Path {
&self.support_crate
}
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 subsystem_generic_types(&self) -> Vec<Ident> {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|sff| sff.generic.clone())
.collect::<Vec<_>>()
}
pub(crate) fn baggage(&self) -> &[BaggageField] {
self.baggage.as_slice()
}
pub(crate) fn baggage_names(&self) -> Vec<Ident> {
self.baggage.iter().map(|bag| bag.field_name.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>>()
}
pub(crate) fn baggage_generic_types(&self) -> Vec<Ident> {
self.baggage
.iter()
.flat_map(|bag| bag.generic_types.clone())
.collect::<Vec<_>>()
}
pub(crate) fn any_message(&self) -> Vec<Path> {
self.subsystems
.iter()
.map(|ssf| ssf.message_to_consume.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.message_to_consume.clone())
.collect::<Vec<_>>()
}
}
/// Internals of the orchestra.
#[derive(Debug, Clone)]
pub(crate) struct OrchestraGuts {
pub(crate) name: Ident,
pub(crate) subsystems: Vec<SubSysField>,
pub(crate) baggage: Vec<BaggageField>,
}
impl OrchestraGuts {
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() {
// collect all subsystem annotations per field
let mut subsystem_attr =
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 field, only named fields are expected.",
)
})?;
// a `#[subsystem(..)]` annotation exists
if let Some((attr_tokens, span)) = subsystem_attr.next() {
if let Some((_attr_tokens2, span2)) = subsystem_attr.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 span = attr_tokens.span();
let attr_tokens = attr_tokens.clone();
let subsystem_attrs: SubSystemAttrItems = syn::parse2(attr_tokens.clone())?;
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. It will be used as a generic.",
)
})?
.clone();
// check for unique subsystem name, otherwise we'd create invalid code:
if let Some(previous) = unique_subsystem_idents.get(&generic) {
let mut e = Error::new(generic.span(), "Duplicate subsystem names");
e.combine(Error::new(previous.span(), "previously defined here."));
return Err(e)
}
unique_subsystem_idents.insert(generic.clone());
let SubSystemAttrItems { wip, blocking, consumes, sends, .. } = subsystem_attrs;
// messages to be sent
let sends = if let Some(sends) = sends {
Vec::from_iter(sends.sends.iter().cloned())
} else {
vec![]
};
// messages deemed for consumption
let consumes = if let Some(consumes) = consumes {
consumes.consumes
} else {
return Err(Error::new(span, "Must provide exactly one consuming message type"))
};
subsystems.push(SubSysField {
name: ident,
generic,
message_to_consume: consumes,
messages_to_send: sends,
wip,
blocking,
});
} else {
let flattened = flatten_type(&ty, ident.span())?;
let generic_types = flattened
.iter()
.filter(|flat_ident| baggage_generics.contains(flat_ident))
.cloned()
.collect::<Vec<_>>();
baggage.push(BaggageField { field_name: ident, generic_types, field_ty: ty, vis });
}
}
Ok(Self { name, subsystems, baggage })
}
}
impl Parse for OrchestraGuts {
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 independent 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.",
)),
}
}
}
@@ -1,143 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::kw;
use proc_macro2::Span;
use quote::{quote, ToTokens};
use std::collections::{hash_map::RandomState, HashMap};
use syn::{
parse::{Parse, ParseBuffer},
punctuated::Punctuated,
spanned::Spanned,
Error, Ident, Path, Result, Token,
};
#[derive(Clone, Debug)]
enum SubsystemAttrItem {
/// Error type provided by the user.
Error { tag: kw::error, eq_token: Token![=], value: Path },
/// For which slot in the orchestra this should be plugged.
///
/// The subsystem implementation can and should have a different name
/// from the declared parameter type in the orchestra.
Subsystem { tag: Option<kw::subsystem>, eq_token: Option<Token![=]>, value: Ident },
/// The prefix to apply when a subsystem is implemented in a different file/crate
/// than the orchestra itself.
///
/// Important for `#[subsystem(..)]` to reference the traits correctly.
TraitPrefix { tag: kw::prefix, eq_token: Token![=], value: Path },
}
impl ToTokens for SubsystemAttrItem {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ts = match self {
Self::TraitPrefix { tag, eq_token, value } => {
quote! { #tag #eq_token, #value }
},
Self::Error { tag, eq_token, value } => {
quote! { #tag #eq_token, #value }
},
Self::Subsystem { tag, eq_token, value } => {
quote! { #tag #eq_token, #value }
},
};
tokens.extend(ts.into_iter());
}
}
impl Parse for SubsystemAttrItem {
fn parse(input: &ParseBuffer) -> Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(kw::error) {
Ok(SubsystemAttrItem::Error {
tag: input.parse::<kw::error>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::prefix) {
Ok(SubsystemAttrItem::TraitPrefix {
tag: input.parse::<kw::prefix>()?,
eq_token: input.parse()?,
value: input.parse()?,
})
} else if lookahead.peek(kw::subsystem) {
Ok(SubsystemAttrItem::Subsystem {
tag: Some(input.parse::<kw::subsystem>()?),
eq_token: Some(input.parse()?),
value: input.parse()?,
})
} else {
Ok(SubsystemAttrItem::Subsystem { tag: None, eq_token: None, value: input.parse()? })
}
}
}
/// Attribute arguments `$args` in `#[subsystem( $args )]`.
#[derive(Clone, Debug)]
pub(crate) struct SubsystemAttrArgs {
span: Span,
pub(crate) error_path: Option<Path>,
pub(crate) subsystem_ident: Ident,
pub(crate) trait_prefix_path: Option<Path>,
}
impl Spanned for SubsystemAttrArgs {
fn span(&self) -> Span {
self.span.clone()
}
}
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| match item {
SubsystemAttrItem::$variant { value, .. } => Some(value.clone()),
_ => None,
})
};
}
impl Parse for SubsystemAttrArgs {
fn parse(input: &ParseBuffer) -> Result<Self> {
let span = input.span();
let items: Punctuated<SubsystemAttrItem, Token![,]> =
input.parse_terminated(SubsystemAttrItem::parse)?;
let mut unique = HashMap::<
std::mem::Discriminant<SubsystemAttrItem>,
SubsystemAttrItem,
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 generation type found"),
);
e.combine(Error::new(first.span(), "previously defined here."));
return Err(e)
}
}
let error_path = extract_variant!(unique, Error);
let subsystem_ident = extract_variant!(unique, Subsystem; err = "Must annotate the identical orchestra error type via `subsystem=..` or plainly as `Subsystem` as specified in the orchestra declaration.")?;
let trait_prefix_path = extract_variant!(unique, TraitPrefix);
Ok(SubsystemAttrArgs { span, error_path, subsystem_ident, trait_prefix_path })
}
}
@@ -1,294 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use super::*;
use crate::{SubSysAttrItem, SubSystemAttrItems};
use assert_matches::assert_matches;
use quote::quote;
use syn::parse_quote;
mod attr {
use super::*;
#[test]
fn attr_full_works() {
let attr: OrchestraAttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=SigSigSig, signal_capacity=111, message_capacity=222,
error=OrchestraError,
};
assert_matches!(attr, OrchestraAttrArgs {
message_channel_capacity,
signal_channel_capacity,
..
} => {
assert_eq!(message_channel_capacity, 222);
assert_eq!(signal_channel_capacity, 111);
});
}
#[test]
fn attr_partial_works() {
let attr: OrchestraAttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=::foo::SigSigSig,
error=OrchestraError,
};
assert_matches!(attr, OrchestraAttrArgs {
message_channel_capacity: _,
signal_channel_capacity: _,
..
} => {
});
}
}
mod strukt {
use super::*;
#[test]
fn parse_subsystem_attr_item_works_00_wip() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
wip
}), Ok(SubSysAttrItem::Wip(_)) => {
});
}
#[test]
fn parse_subsystem_attr_item_works_02_sends() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
sends: [A, B, C]
}), Ok(SubSysAttrItem::Sends(sends)) => {
assert_eq!(sends.sends.len(), 3);
});
}
#[test]
fn parse_subsystem_attr_item_works_03_sends() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
sends: [A]
}), Ok(SubSysAttrItem::Sends(sends)) => {
assert_eq!(sends.sends.len(), 1);
});
}
#[test]
fn parse_subsystem_attr_item_works_04_sends() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
sends: [A,]
}), Ok(SubSysAttrItem::Sends(sends)) => {
assert_eq!(sends.sends.len(), 1);
});
}
#[test]
fn parse_subsystem_attr_item_works_05_sends() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
sends: []
}), Ok(SubSysAttrItem::Sends(sends)) => {
assert_eq!(sends.sends.len(), 0);
});
}
#[test]
fn parse_subsystem_attr_item_works_06_consumes() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
consumes: Foo
}), Ok(SubSysAttrItem::Consumes(_consumes)) => {
});
}
#[test]
fn parse_subsystem_attr_item_works_07_consumes() {
assert_matches!(
syn::parse2::<SubSysAttrItem>(quote! {
Foo
}), Ok(SubSysAttrItem::Consumes(_consumes)) => {
});
}
#[test]
fn parse_subsystem_attributes_works_00() {
syn::parse2::<SubSystemAttrItems>(quote! {
(wip, blocking, consumes: Foo, sends: [])
})
.unwrap();
}
#[test]
fn parse_subsystem_attributes_works_01() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(blocking, Foo, sends: [])
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_02() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(consumes: Foo, sends: [Bar])
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_03() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(blocking, consumes: Foo, sends: [Bar])
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_04() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(wip, consumes: Foo, sends: [Bar])
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_05() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(consumes: Foo)
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_06() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(sends: [Foo], consumes: Bar)
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_07_duplicate_send() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(sends: [Foo], Bar, Y)
}), Err(e) => {
dbg!(e)
});
}
#[test]
fn parse_subsystem_attributes_works_08() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(sends: [Foo], consumes: Bar)
}), Ok(_) => {
});
}
#[test]
fn parse_subsystem_attributes_works_09_neither_consumes_nor_sends() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(sends: [])
}), Err(e) => {
// must either consume smth or sends smth, neither is NOK
dbg!(e)
});
}
#[test]
fn parse_subsystem_attributes_works_10_empty_with_braces() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
()
}), Err(e) => {
dbg!(e)
});
}
#[test]
fn parse_subsystem_attributes_works_11_empty() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
}), Err(e) => {
dbg!(e)
});
}
#[test]
fn parse_subsystem_attributes_works_12_duplicate_consumes_different_fmt() {
assert_matches!(
syn::parse2::<SubSystemAttrItems>(quote! {
(Foo, consumes = Foo)
}), Err(e) => {
dbg!(e)
});
}
#[test]
fn struct_parse_baggage() {
let item: OrchestraGuts = parse_quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(consumes: Foo, sends: [])]
sub0: FooSubsystem,
metrics: Metrics,
}
};
let _ = dbg!(item);
}
#[test]
fn struct_parse_full() {
let item: OrchestraGuts = parse_quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(consumes: Foo, sends: [])]
sub0: FooSubsystem,
#[subsystem(blocking, consumes: Bar, sends: [])]
yyy: BaersBuyBilliardBalls,
#[subsystem(blocking, consumes: Twain, sends: [])]
fff: Beeeeep,
#[subsystem(consumes: Rope)]
mc: MountainCave,
metrics: Metrics,
}
};
let _ = dbg!(item);
}
#[test]
fn struct_parse_basic() {
let item: OrchestraGuts = parse_quote! {
pub struct Ooooh {
#[subsystem(consumes: Foo, sends: [])]
sub0: FooSubsystem,
}
};
let _ = dbg!(item);
}
}
@@ -1,309 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Generates the bounds for a particular subsystem `Context` and associate `type Sender`.
//!
//!
//! ## Implement `trait Subsystem<Context, Error>` via `subsystem`
//!
//! ```ignore
//! # use orchestra_proc_macro::subsystem;
//! # mod somewhere {
//! # use orchestra_proc_macro::orchestra;
//! # pub use orchestra::*;
//! #
//! # #[derive(Debug, thiserror::Error)]
//! # #[error("Yikes!")]
//! # pub struct Yikes;
//! # impl From<OrchestraError> for Yikes {
//! # fn from(_: OrchestraError) -> Yikes { Yikes }
//! # }
//! # impl From<mpsc::SendError> for Yikes {
//! # fn from(_: mpsc::SendError) -> Yikes { Yikes }
//! # }
//! #
//! # #[derive(Debug)]
//! # pub struct Eve;
//! #
//! # #[derive(Debug, Clone)]
//! # pub struct Sig;
//! #
//! # #[derive(Debug, Clone, Copy)]
//! # pub struct A;
//! # #[derive(Debug, Clone, Copy)]
//! # pub struct B;
//! #
//! # #[orchestra(signal=Sig, gen=AllOfThem, event=Eve, error=Yikes)]
//! # pub struct Wonderland {
//! # #[subsystem(A, sends: [B])]
//! # foo: Foo,
//! # #[subsystem(B, sends: [A])]
//! # bar: Bar,
//! # }
//! # }
//! # use somewhere::{Yikes, SpawnedSubsystem};
//! #
//! # struct FooSubsystem;
//! #
//! #[subsystem(Foo, error = Yikes, prefix = somewhere)]
//! impl<Context> FooSubsystem {
//! fn start(self, context: Context) -> SpawnedSubsystem<Yikes> {
//! // ..
//! # let _ = context;
//! # unimplemented!()
//! }
//! }
//! ```
//!
//! expands to
//!
//! ```ignore
//! # use orchestra_proc_macro::subsystem;
//! # mod somewhere {
//! # use orchestra_proc_macro::orchestra;
//! # pub use orchestra::*;
//! #
//! # #[derive(Debug, thiserror::Error)]
//! # #[error("Yikes!")]
//! # pub struct Yikes;
//! # impl From<OrchestraError> for Yikes {
//! # fn from(_: OrchestraError) -> Yikes { Yikes }
//! # }
//! # impl From<mpsc::SendError> for Yikes {
//! # fn from(_: mpsc::SendError) -> Yikes { Yikes }
//! # }
//! #
//! # #[derive(Debug)]
//! # pub struct Eve;
//! #
//! # #[derive(Debug, Clone)]
//! # pub struct Sig;
//! #
//! # #[derive(Debug, Clone, Copy)]
//! # pub struct A;
//! # #[derive(Debug, Clone, Copy)]
//! # pub struct B;
//! #
//! # #[orchestra(signal=Sig, gen=AllOfThem, event=Eve, error=Yikes)]
//! # pub struct Wonderland {
//! # #[subsystem(A, sends: [B])]
//! # foo: Foo,
//! # #[subsystem(B, sends: [A])]
//! # bar: Bar,
//! # }
//! # }
//! # use somewhere::{Yikes, SpawnedSubsystem};
//! # use orchestra as support_crate;
//! #
//! # struct FooSubsystem;
//! #
//! impl<Context> support_crate::Subsystem<Context, Yikes> for FooSubsystem
//! where
//! Context: somewhere::FooContextTrait,
//! Context: support_crate::SubsystemContext,
//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
//! {
//! fn start(self, context: Context) -> SpawnedSubsystem<Yikes> {
//! // ..
//! # let _ = context;
//! # unimplemented!()
//! }
//! }
//! ```
//!
//! where `support_crate` is either equivalent to `somewhere` or derived from the cargo manifest.
//!
//!
//! ## Add additional trait bounds for a generic `Context` via `contextbounds`
//!
//! ### To an `ImplItem`
//!
//! ```ignore
//! #[contextbounds(Foo, prefix = somewhere)]
//! impl<Context> X {
//! ..
//! }
//! ```
//!
//! expands to
//!
//! ```ignore
//! impl<Context> X
//! where
//! Context: somewhere::FooSubsystemTrait,
//! Context: support_crate::SubsystemContext,
//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
//! {
//! }
//! ```
//!
//! ### To a free standing `Fn` (not a method, that's covered by the above)
//!
//! ```ignore
//! #[contextbounds(Foo, prefix = somewhere)]
//! fn do_smth<Context>(context: &mut Context) {
//! ..
//! }
//! ```
//!
//! expands to
//!
//! ```ignore
//! fn do_smth<Context>(context: &mut Context)
//! where
//! Context: somewhere::FooSubsystemTrait,
//! Context: support_crate::SubsystemContext,
//! <Context as somewhere::FooContextTrait>::Sender: somewhere::FooSenderTrait,
//! <Context as support_crate::SubsystemContext>::Sender: somewhere::FooSenderTrait,
//! {
//! }
//! ```
use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens};
use syn::{parse2, parse_quote, punctuated::Punctuated, Result};
use super::{parse::*, *};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MakeSubsystem {
/// Implements `trait Subsystem` and apply the trait bounds to the `Context` generic.
///
/// Relevant to `impl Item` only.
ImplSubsystemTrait,
/// Only apply the trait bounds to the context.
AddContextTraitBounds,
}
pub(crate) fn impl_subsystem_context_trait_bounds(
attr: TokenStream,
orig: TokenStream,
make_subsystem: MakeSubsystem,
) -> Result<proc_macro2::TokenStream> {
let args = parse2::<SubsystemAttrArgs>(attr.clone())?;
let span = args.span();
let SubsystemAttrArgs { error_path, subsystem_ident, trait_prefix_path, .. } = args;
let mut item = parse2::<syn::Item>(orig)?;
// always prefer the direct usage, if it's not there, let's see if there is
// a `prefix=*` provided. Either is ok.
// Technically this is two different things:
// The place where the `#[orchestra]` is annotated is where all `trait *SenderTrait` and
// `trait *ContextTrait` types exist.
// The other usage is the true support crate `orchestra`, where the static ones
// are declared.
// Right now, if the `support_crate` is not included, it falls back silently to the `trait_prefix_path`.
let support_crate = support_crate()
.or_else(|_e| {
trait_prefix_path.clone().ok_or_else(|| {
syn::Error::new(attr.span(), "Couldn't find `orchestra` in manifest, but also missing a `prefix=` to help trait bound resolution")
})
})?;
let trait_prefix_path = trait_prefix_path.unwrap_or_else(|| parse_quote! { self });
if trait_prefix_path.segments.trailing_punct() {
return Err(syn::Error::new(trait_prefix_path.span(), "Must not end with `::`"))
}
let subsystem_ctx_trait = format_ident!("{}ContextTrait", subsystem_ident);
let subsystem_sender_trait = format_ident!("{}SenderTrait", subsystem_ident);
let extra_where_predicates: Punctuated<syn::WherePredicate, syn::Token![,]> = parse_quote! {
Context: #trait_prefix_path::#subsystem_ctx_trait,
Context: #support_crate::SubsystemContext,
<Context as #trait_prefix_path::#subsystem_ctx_trait>::Sender: #trait_prefix_path::#subsystem_sender_trait,
<Context as #support_crate::SubsystemContext>::Sender: #trait_prefix_path::#subsystem_sender_trait,
};
let apply_ctx_bound_if_present = move |generics: &mut syn::Generics| -> bool {
if generics
.params
.iter()
.find(|generic| match generic {
syn::GenericParam::Type(ty) if ty.ident == "Context" => true,
_ => false,
})
.is_some()
{
let where_clause = generics.make_where_clause();
where_clause.predicates.extend(extra_where_predicates.clone());
true
} else {
false
}
};
match item {
syn::Item::Impl(ref mut struktured_impl) => {
if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
let error_path = error_path.ok_or_else(|| {
syn::Error::new(
span,
"Must annotate the identical orchestra error type via `error=..`.",
)
})?;
// Only replace the subsystem trait if it's desired.
struktured_impl.trait_.replace((
None,
parse_quote! {
#support_crate::Subsystem<Context, #error_path>
},
syn::token::For::default(),
));
}
apply_ctx_bound_if_present(&mut struktured_impl.generics);
for item in struktured_impl.items.iter_mut() {
match item {
syn::ImplItem::Method(method) => {
apply_ctx_bound_if_present(&mut method.sig.generics);
},
_others => {
// don't error, just nop
},
}
}
},
syn::Item::Fn(ref mut struktured_fn) => {
if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
return Err(syn::Error::new(struktured_fn.span(), "Cannot make a free function a subsystem, did you mean to apply `contextbound` instead?"))
}
apply_ctx_bound_if_present(&mut struktured_fn.sig.generics);
},
other =>
return Err(syn::Error::new(
other.span(),
"Macro can only be annotated on functions or struct implementations",
)),
};
Ok(item.to_token_stream())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_path() {
let _p: Path = parse_quote! { self };
let _p: Path = parse_quote! { crate };
let _p: Path = parse_quote! { ::foo };
let _p: Path = parse_quote! { bar };
}
}
@@ -1,116 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
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=OrchestraError,
};
let item = quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(Foo)]
sub0: FooSubsystem,
#[subsystem(blocking, Bar)]
yyy: BaersBuyBilliardBalls,
#[subsystem(blocking, Twain)]
fff: Beeeeep,
#[subsystem(Rope)]
mc: MountainCave,
metrics: Metrics,
}
};
let output = impl_orchestra_gen(attr, item).expect("Simple example always works. qed");
println!("//generated:");
println!("{}", output);
}
#[test]
fn struct_parse_full() {
let item: OrchestraGuts = parse_quote! {
pub struct Ooooh<X = Pffffffft> where X: Secrit {
#[subsystem(Foo)]
sub0: FooSubsystem,
#[subsystem(blocking, Bar)]
yyy: BaersBuyBilliardBalls,
#[subsystem(blocking, Twain)]
fff: Beeeeep,
#[subsystem(Rope)]
mc: MountainCave,
metrics: Metrics,
}
};
let _ = dbg!(item);
}
#[test]
fn struct_parse_basic() {
let item: OrchestraGuts = parse_quote! {
pub struct Ooooh {
#[subsystem(Foo)]
sub0: FooSubsystem,
}
};
let _ = dbg!(item);
}
#[test]
fn attr_full() {
let attr: OrchestraAttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=SigSigSig, signal_capacity=111, message_capacity=222,
error=OrchestraError,
};
assert_matches!(attr, OrchestraAttrArgs {
message_channel_capacity,
signal_channel_capacity,
..
} => {
assert_eq!(message_channel_capacity, 222);
assert_eq!(signal_channel_capacity, 111);
});
}
#[test]
fn attr_partial() {
let attr: OrchestraAttrArgs = parse_quote! {
gen=AllMessage, event=::some::why::ExternEvent, signal=::foo::SigSigSig,
error=OrchestraError,
};
assert_matches!(attr, OrchestraAttrArgs {
message_channel_capacity: _,
signal_channel_capacity: _,
..
} => {
});
}
-549
View File
@@ -1,549 +0,0 @@
// Copyright (C) 2021 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! # Orchestra
//!
//! `orchestra` provides a global information flow of what a token of information.
//! The token is arbitrary, but is used to notify all `Subsystem`s of what is relevant
//! and what is not.
//!
//! For the motivations behind implementing the orchestra itself you should
//! check out that guide, documentation in this crate will focus and be of
//! technical nature.
//!
//! An `Orchestra` is something that allows spawning/stopping and orchestrating
//! 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 `Orchestra` is instantiated with a pre-defined set of `Subsystems` that
//! share the same behavior from `Orchestra`'s point of view.
//!
//! ```text
//! +-----------------------------+
//! | Orchesta |
//! +-----------------------------+
//!
//! ................| Orchestra "holds" these and uses |.............
//! . them to (re)start things .
//! . .
//! . +-------------------+ +---------------------+ .
//! . | Subsystem1 | | Subsystem2 | .
//! . +-------------------+ +---------------------+ .
//! . | | .
//! ..................................................................
//! | |
//! start() start()
//! V V
//! ..................| Orchestra "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 orchestra_proc_macro::{contextbounds, orchestra, subsystem};
#[doc(hidden)]
pub use metered;
#[doc(hidden)]
pub use tracing;
#[doc(hidden)]
pub use async_trait::async_trait;
#[doc(hidden)]
pub use futures::{
self,
channel::{mpsc, oneshot},
future::{BoxFuture, Fuse, Future},
poll, select,
stream::{self, select, select_with_strategy, FuturesUnordered, PollNext},
task::{Context, Poll},
FutureExt, StreamExt,
};
#[doc(hidden)]
pub use std::pin::Pin;
use std::sync::{
atomic::{self, AtomicUsize},
Arc,
};
#[doc(hidden)]
pub use std::time::Duration;
#[doc(hidden)]
pub use futures_timer::Delay;
use std::fmt;
#[cfg(test)]
mod tests;
/// A spawner
#[dyn_clonable::clonable]
pub trait Spawner: Clone + Send + Sync {
/// Spawn the given blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn_blocking(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
/// Spawn the given non-blocking future.
///
/// The given `group` and `name` is used to identify the future in tracing.
fn spawn(
&self,
name: &'static str,
group: Option<&'static str>,
future: futures::future::BoxFuture<'static, ()>,
);
}
/// A type of messages that are sent from a [`Subsystem`] to the declared orchestra.
///
/// Used to launch jobs.
pub enum ToOrchestra {
/// A message that wraps something the `Subsystem` is desiring to
/// spawn on the orchestra 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,
/// Subsystem of the task to spawn which be shown in jaeger and tracing logs.
subsystem: Option<&'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,
/// Subsystem of the task to spawn which be shown in jaeger and tracing logs.
subsystem: Option<&'static str>,
/// The future to execute.
s: BoxFuture<'static, ()>,
},
}
impl fmt::Debug for ToOrchestra {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SpawnJob { name, subsystem, .. } => {
writeln!(f, "SpawnJob{{ {}, {} ..}}", name, subsystem.unwrap_or("default"))
},
Self::SpawnBlockingJob { name, subsystem, .. } => {
writeln!(f, "SpawnBlockingJob{{ {}, {} ..}}", name, subsystem.unwrap_or("default"))
},
}
}
}
/// 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 }
}
/// A functor to specify strategy of the channels selection in the `SubsystemIncomingMessages`
pub fn select_message_channel_strategy(_: &mut ()) -> PollNext {
PollNext::Right
}
/// Incoming messages from both the bounded and unbounded channel.
pub type SubsystemIncomingMessages<M> = self::stream::SelectWithStrategy<
self::metered::MeteredReceiver<MessagePacket<M>>,
self::metered::UnboundedMeteredReceiver<MessagePacket<M>>,
fn(&mut ()) -> self::stream::PollNext,
(),
>;
/// 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 {
// It's imperative that we prevent reading a stale value from memory because of reordering.
// Memory barrier to ensure that no reads or writes in the current thread before this load are reordered.
// All writes in other threads using release semantics become visible to the current thread.
self.0.load(atomic::Ordering::Acquire)
}
/// Increase the number of signals by one.
pub fn inc(&self) {
self.0.fetch_add(1, atomic::Ordering::AcqRel);
}
}
/// 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 new type wrapping a `BoxFuture`.
pub struct SpawnedSubsystem<E>
where
E: std::error::Error + Send + Sync + 'static + From<self::OrchestraError>,
{
/// 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 OrchestraError {
#[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 `OrchestraError`.
pub type OrchestraResult<T> = std::result::Result<T, self::OrchestraError>;
/// 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` or `#message_wrapper` type.
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 orchestra.
/// It wraps signals from an orchestra 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 FromOrchestra<Message, Signal> {
/// Signal from the `Orchestra`.
Signal(Signal),
/// Some other `Subsystem`'s message.
Communication {
/// Contained message
msg: Message,
},
}
impl<Signal, Message> From<Signal> for FromOrchestra<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.
///
/// [`Orchestra`]: struct.Orchestra.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 messages `enum` for this particular subsystem.
type OutgoingMessages: ::std::fmt::Debug + Send + 'static;
// The overarching messages `enum` for this particular subsystem.
// type AllMessages: From<Self::OutgoingMessages> + From<Self::Message> + std::fmt::Debug + Send + 'static;
/// The sender type as provided by `sender()` and underlying.
type Sender: Clone + Send + 'static + SubsystemSender<Self::OutgoingMessages>;
/// The error type.
type Error: ::std::error::Error + ::std::convert::From<OrchestraError> + Sync + Send + 'static;
/// Try to asynchronously receive a message.
///
/// 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<FromOrchestra<Self::Message, Self::Signal>>, ()>;
/// Receive a message.
async fn recv(&mut self) -> Result<FromOrchestra<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.
// #[deprecated(note = "Use `self.sender().send_message(msg) instead, avoid passing around the full context.")]
async fn send_message<T>(&mut self, msg: T)
where
Self::OutgoingMessages: From<T> + Send,
T: Send,
{
self.sender().send_message(<Self::OutgoingMessages>::from(msg)).await
}
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
// #[deprecated(note = "Use `self.sender().send_message(msg) instead, avoid passing around the full context.")]
async fn send_messages<T, I>(&mut self, msgs: I)
where
Self::OutgoingMessages: From<T> + Send,
I: IntoIterator<Item = T> + Send,
I::IntoIter: Send,
T: Send,
{
self.sender()
.send_messages(msgs.into_iter().map(<Self::OutgoingMessages>::from))
.await
}
/// Send a message using the unbounded connection.
// #[deprecated(note = "Use `self.sender().send_unbounded_message(msg) instead, avoid passing around the full context.")]
fn send_unbounded_message<X>(&mut self, msg: X)
where
Self::OutgoingMessages: From<X> + Send,
X: Send,
{
self.sender().send_unbounded_message(<Self::OutgoingMessages>::from(msg))
}
/// Obtain the sender.
fn sender(&mut self) -> &mut Self::Sender;
}
/// A trait that describes the [`Subsystem`]s that can run on the [`Orchestra`].
///
/// 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.
///
/// [`Orchestra`]: struct.Orchestra.html
/// [`Subsystem`]: trait.Subsystem.html
pub trait Subsystem<Ctx, E>
where
Ctx: SubsystemContext,
E: std::error::Error + Send + Sync + 'static + From<self::OrchestraError>,
{
/// 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<OutgoingMessage>: Clone + Send + 'static
where
OutgoingMessage: Send,
{
/// Send a direct message to some other `Subsystem`, routed based on message type.
async fn send_message(&mut self, msg: OutgoingMessage);
/// Send multiple direct messages to other `Subsystem`s, routed based on message type.
async fn send_messages<I>(&mut self, msgs: I)
where
I: IntoIterator<Item = OutgoingMessage> + Send,
I::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: OutgoingMessage);
}
/// 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
}
}
-38
View File
@@ -1,38 +0,0 @@
// Copyright (C) 2022 Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#[test]
#[rustversion::attr(not(stable), ignore)]
fn ui_compile_fail() {
// Only run the ui tests when `RUN_UI_TESTS` is set.
if std::env::var("RUN_UI_TESTS").is_err() {
return
}
let t = trybuild::TestCases::new();
t.compile_fail("tests/ui/err-*.rs");
}
#[test]
#[rustversion::attr(not(stable), ignore)]
fn ui_pass() {
// Only run the ui tests when `RUN_UI_TESTS` is set.
if std::env::var("RUN_UI_TESTS").is_err() {
return
}
let t = trybuild::TestCases::new();
t.pass("tests/ui/ok-*.rs");
}
@@ -1,38 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Default)]
struct AwesomeSubSys2;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone)]
struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
#[subsystem(MsgStrukt)]
sub1: AwesomeSubSys2,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let orchestra = Orchestra::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -1,21 +0,0 @@
error[E0119]: conflicting implementations of trait `std::convert::From<MsgStrukt>` for type `AllMessages`
--> tests/ui/err-01-duplicate-consumer.rs:19:1
|
19 | #[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `AllMessages`
|
= note: this error originates in the attribute macro `orchestra` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0119]: conflicting implementations of trait `AssociateOutgoing` for type `MsgStrukt`
--> tests/ui/err-01-duplicate-consumer.rs:19:1
|
19 | #[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| first implementation here
| conflicting implementation for `MsgStrukt`
|
= note: this error originates in the attribute macro `orchestra` (in Nightly builds, run with -Z macro-backtrace for more info)
@@ -1,32 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
struct SigSigSig;
struct Event;
#[derive(Clone, Debug)]
struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
enum Orchestra {
#[subsystem(MsgStrukt)]
Sub0(AwesomeSubSys),
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let orchestra = Orchestra::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -1,11 +0,0 @@
error: expected `struct`
--> tests/ui/err-02-enum.rs:16:1
|
16 | enum Orchestra {
| ^^^^
error[E0433]: failed to resolve: use of undeclared type `Orchestra`
--> tests/ui/err-02-enum.rs:27:18
|
27 | let orchestra = Orchestra::<_,_>::builder()
| ^^^^^^^^^ use of undeclared type `Orchestra`
@@ -1,39 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone, Debug)]
struct MsgStrukt(u8);
#[derive(Clone, Debug)]
struct MsgStrukt2(f64);
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
#[subsystem(MsgStrukt2)]
sub1: AwesomeSubSys,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let orchestra = Orchestra::<_,_>::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build(|| -> DummyCtx { DummyCtx } );
}
@@ -1,17 +0,0 @@
error: Duplicate subsystem names
--> tests/ui/err-03-subsys-twice.rs:25:8
|
25 | sub1: AwesomeSubSys,
| ^^^^^^^^^^^^^
error: previously defined here.
--> tests/ui/err-03-subsys-twice.rs:22:8
|
22 | sub0: AwesomeSubSys,
| ^^^^^^^^^^^^^
error[E0433]: failed to resolve: use of undeclared type `Orchestra`
--> tests/ui/err-03-subsys-twice.rs:34:18
|
34 | let orchestra = Orchestra::<_,_>::builder()
| ^^^^^^^^^ use of undeclared type `Orchestra`
@@ -1,36 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
#[derive(Clone, Debug)]
struct SigSigSig;
struct Event;
#[derive(Clone)]
struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
struct DummySpawner;
struct DummyCtx;
fn main() {
let _ = Orchestra::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -1,13 +0,0 @@
error: Must declare the orchestra error type via `error=..`.
--> tests/ui/err-04-missing-error.rs:16:1
|
16 | #[orchestra(signal=SigSigSig, event=Event, gen=AllMessages)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
= note: this error originates in the attribute macro `orchestra` (in Nightly builds, run with -Z macro-backtrace for more info)
error[E0433]: failed to resolve: use of undeclared type `Orchestra`
--> tests/ui/err-04-missing-error.rs:30:10
|
30 | let _ = Orchestra::builder()
| ^^^^^^^^^ use of undeclared type `Orchestra`
@@ -1,61 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgStrukt>, OrchestraError> for AwesomeSubSys {
fn start(self, _ctx: OrchestraSubsystemContext<MsgStrukt>) -> SpawnedSubsystem<OrchestraError> {
unimplemented!("starting yay!")
}
}
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
struct DummyCtx;
fn main() {
let _ = Orchestra::builder()
.sub0(AwesomeSubSys::default())
//.i_like_pie(std::f64::consts::PI) // The filed is not initialised
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -1,11 +0,0 @@
error[E0599]: no method named `build` found for struct `OrchestraBuilder<Init<DummySpawner>, Init<AwesomeSubSys>, Missing<f64>>` in the current scope
--> tests/ui/err-05-missing-field.rs:59:4
|
22 | #[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
| ---------------------------------------------------------------------------------- method `build` not found for this struct
...
59 | .build()
| ^^^^^ method not found in `OrchestraBuilder<Init<DummySpawner>, Init<AwesomeSubSys>, Missing<f64>>`
|
= note: the method was found for
- `OrchestraBuilder<Init<S>, Init<AwesomeSubSys>, Init<f64>>`
@@ -1,61 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgStrukt>, OrchestraError> for AwesomeSubSys {
fn start(self, _ctx: OrchestraSubsystemContext<MsgStrukt>) -> SpawnedSubsystem<OrchestraError> {
unimplemented!("starting yay!")
}
}
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
struct DummyCtx;
fn main() {
let _ = Orchestra::builder()
//.sub0(AwesomeSubSys::default()) // Subsystem is uninitialized
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -1,11 +0,0 @@
error[E0599]: no method named `build` found for struct `OrchestraBuilder<Init<DummySpawner>, Missing<_>, Init<f64>>` in the current scope
--> tests/ui/err-06-missing-subsystem.rs:59:4
|
22 | #[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
| ---------------------------------------------------------------------------------- method `build` not found for this struct
...
59 | .build()
| ^^^^^ method not found in `OrchestraBuilder<Init<DummySpawner>, Missing<_>, Init<f64>>`
|
= note: the method was found for
- `OrchestraBuilder<Init<S>, Init<AwesomeSubSys>, Init<f64>>`
@@ -1,61 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgStrukt>, OrchestraError> for AwesomeSubSys {
fn start(self, _ctx: OrchestraSubsystemContext<MsgStrukt>) -> SpawnedSubsystem<OrchestraError> {
unimplemented!("starting yay!")
}
}
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
struct DummyCtx;
fn main() {
let _ = Orchestra::builder()
.sub0(AwesomeSubSys::default())
.i_like_pie(std::f64::consts::PI)
//.spawner(DummySpawner) // Spawner is missing
.build()
.unwrap();
}
@@ -1,11 +0,0 @@
error[E0599]: no method named `build` found for struct `OrchestraBuilder<Missing<_>, Init<AwesomeSubSys>, Init<f64>>` in the current scope
--> tests/ui/err-07-missing-spawner.rs:59:4
|
22 | #[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
| ---------------------------------------------------------------------------------- method `build` not found for this struct
...
59 | .build()
| ^^^^^ method not found in `OrchestraBuilder<Missing<_>, Init<AwesomeSubSys>, Init<f64>>`
|
= note: the method was found for
- `OrchestraBuilder<Init<S>, Init<AwesomeSubSys>, Init<f64>>`
@@ -1,62 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgStrukt>, OrchestraError> for AwesomeSubSys {
fn start(self, _ctx: OrchestraSubsystemContext<MsgStrukt>) -> SpawnedSubsystem<OrchestraError> {
unimplemented!("starting yay!")
}
}
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
struct Orchestra {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: f64,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
struct DummyCtx;
fn main() {
let _ = Orchestra::builder()
.sub0(AwesomeSubSys::default())
.sub0(AwesomeSubSys::default()) // Duplicate subsystem
.i_like_pie(std::f64::consts::PI)
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -1,10 +0,0 @@
error[E0599]: no method named `sub0` found for struct `OrchestraBuilder<Missing<_>, Init<AwesomeSubSys>, Missing<f64>>` in the current scope
--> tests/ui/err-08-duplicate-subsystem.rs:57:4
|
22 | #[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
| ---------------------------------------------------------------------------------- method `sub0` not found for this struct
...
57 | .sub0(AwesomeSubSys::default()) // Duplicate subsystem
| ^^^^-------------------------- help: remove the arguments
| |
| field, not a method
@@ -1,61 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSys;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgStrukt>, OrchestraError> for AwesomeSubSys {
fn start(self, _ctx: OrchestraSubsystemContext<MsgStrukt>) -> SpawnedSubsystem<OrchestraError> {
unimplemented!("starting yay!")
}
}
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgStrukt(u8);
#[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
struct Orchestra<T> {
#[subsystem(MsgStrukt)]
sub0: AwesomeSubSys,
i_like_pie: T,
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
unimplemented!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
struct DummyCtx;
fn main() {
let (_, _): (Orchestra<_, f64>, _) = Orchestra::builder()
.sub0(AwesomeSubSys::default())
//.i_like_pie(std::f64::consts::PI) // The filed is not initialised
.spawner(DummySpawner)
.build()
.unwrap();
}
@@ -1,11 +0,0 @@
error[E0599]: no method named `build` found for struct `OrchestraBuilder<Init<DummySpawner>, Init<AwesomeSubSys>, Missing<_>>` in the current scope
--> tests/ui/err-09-uninit_generic_baggage.rs:59:4
|
22 | #[orchestra(signal=SigSigSig, error=OrchestraError, event=Event, gen=AllMessages)]
| ---------------------------------------------------------------------------------- method `build` not found for this struct
...
59 | .build()
| ^^^^^ method not found in `OrchestraBuilder<Init<DummySpawner>, Init<AwesomeSubSys>, Missing<_>>`
|
= note: the method was found for
- `OrchestraBuilder<Init<S>, Init<AwesomeSubSys>, Init<T>>`
@@ -1,74 +0,0 @@
#![allow(dead_code)]
use orchestra::*;
#[derive(Default)]
struct AwesomeSubSysA;
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgA>, OrchestraError> for AwesomeSubSysA {
fn start(self, _ctx: OrchestraSubsystemContext<MsgA>) -> SpawnedSubsystem<OrchestraError> {
SpawnedSubsystem { name: "sub A", future: Box::pin(async move { Ok(()) }) }
}
}
impl ::orchestra::Subsystem<OrchestraSubsystemContext<MsgB>, OrchestraError> for AwesomeSubSysB {
fn start(self, _ctx: OrchestraSubsystemContext<MsgB>) -> SpawnedSubsystem<OrchestraError> {
SpawnedSubsystem { name: "sub B", future: Box::pin(async move { Ok(()) }) }
}
}
#[derive(Debug, Clone)]
pub struct DummySpawner;
impl Spawner for DummySpawner {
fn spawn_blocking(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
println!("spawn blocking {} {}", task_name, subsystem_name.unwrap_or("default"))
}
fn spawn(
&self,
task_name: &'static str,
subsystem_name: Option<&'static str>,
_future: futures::future::BoxFuture<'static, ()>,
) {
println!("spawn {} {}", task_name, subsystem_name.unwrap_or("default"))
}
}
#[derive(Default)]
pub struct AwesomeSubSysB;
#[derive(Clone, Debug)]
pub struct SigSigSig;
pub struct Event;
#[derive(Clone, Debug)]
pub struct MsgA(u8);
#[derive(Clone, Debug)]
pub struct MsgB(u8);
#[orchestra(signal=SigSigSig, event=Event, gen=AllMessages, error=OrchestraError)]
pub struct Orchestra {
#[subsystem(MsgA)]
sub_a: AwesomeSubSysA,
#[subsystem(wip, MsgB)]
sub_b: AwesomeSubSysB,
}
pub struct DummyCtx;
fn main() {
let _orchestra_builder = Orchestra::builder()
.sub_a(AwesomeSubSysA::default())
// b is tagged as `wip`
// .sub_b(AwesomeSubSysB::default())
.spawner(DummySpawner)
.build();
}
+3 -2
View File
@@ -15,7 +15,7 @@ polkadot-node-primitives = { path = "../primitives" }
polkadot-node-subsystem-types = { path = "../subsystem-types" }
polkadot-node-metrics = { path = "../metrics" }
polkadot-primitives = { path = "../../primitives" }
orchestra = { path = "../orchestra" }
orchestra = "0.0.2"
gum = { package = "tracing-gum", path = "../gum" }
lru = "0.7"
parity-util-mem = { version = "0.11.0", default-features = false }
@@ -23,7 +23,8 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
async-trait = "0.1.57"
[dev-dependencies]
metered = { package = "prioritized-metered-channel", path = "../metered-channel" }
metered = { package = "prioritized-metered-channel", version = "0.2.0" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
futures = { version = "0.3.21", features = ["thread-pool"] }
femme = "2.2.1"
+1 -1
View File
@@ -13,7 +13,7 @@ polkadot-node-primitives = { path = "../primitives" }
polkadot-node-network-protocol = { path = "../network/protocol" }
polkadot-statement-table = { path = "../../statement-table" }
polkadot-node-jaeger = { path = "../jaeger" }
orchestra = { path = "../orchestra" }
orchestra = "0.0.2"
sc-network = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-consensus-babe = { git = "https://github.com/paritytech/substrate", branch = "master" }
+1 -1
View File
@@ -26,7 +26,7 @@ polkadot-node-network-protocol = { path = "../network/protocol" }
polkadot-primitives = { path = "../../primitives" }
polkadot-node-primitives = { path = "../primitives" }
polkadot-overseer = { path = "../overseer" }
metered = { package = "prioritized-metered-channel", path = "../metered-channel" , "version" = "0.2.0" }
metered = { package = "prioritized-metered-channel", version = "0.2.0" }
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
sp-application-crypto = { git = "https://github.com/paritytech/substrate", branch = "master" }