mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 02:57:57 +00:00
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:
Generated
+8
-12
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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" }
|
||||
|
||||
@@ -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"]
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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("e! {
|
||||
#outgoing_wrapper
|
||||
}));
|
||||
}
|
||||
|
||||
ts.extend(wrapped("e! {
|
||||
()
|
||||
}));
|
||||
|
||||
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: _,
|
||||
..
|
||||
} => {
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in New Issue
Block a user