diff --git a/Cargo.lock b/Cargo.lock index 8d3b468..dc17b04 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,12 +17,32 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.3.0" @@ -50,6 +70,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.1" @@ -68,6 +94,106 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.29.0" @@ -80,6 +206,12 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -107,8 +239,24 @@ name = "mad" version = "0.1.0" dependencies = [ "anyhow", + "async-trait", + "futures", + "futures-util", + "rand", + "thiserror", "tokio", + "tokio-util", "tracing", + "tracing-test", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", ] [[package]] @@ -139,10 +287,20 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.36.2" +name = "nu-ansi-term" +version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.36.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" dependencies = [ "memchr", ] @@ -153,6 +311,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "parking_lot" version = "0.12.3" @@ -182,6 +346,21 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -200,6 +379,36 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + [[package]] name = "redox_syscall" version = "0.5.3" @@ -209,6 +418,50 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -221,6 +474,15 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -230,6 +492,15 @@ dependencies = [ "libc", ] +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -257,6 +528,36 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.39.2" @@ -286,6 +587,19 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "tracing" version = "0.1.40" @@ -316,6 +630,57 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tracing-test" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +dependencies = [ + "tracing-core", + "tracing-subscriber", + "tracing-test-macro", +] + +[[package]] +name = "tracing-test-macro" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +dependencies = [ + "quote", + "syn", ] [[package]] @@ -324,12 +689,40 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" version = "0.52.0" @@ -402,3 +795,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/crates/mad/Cargo.toml b/crates/mad/Cargo.toml index de2f540..5b6fd92 100644 --- a/crates/mad/Cargo.toml +++ b/crates/mad/Cargo.toml @@ -5,5 +5,14 @@ edition = "2021" [dependencies] anyhow.workspace = true +async-trait = "0.1.81" +futures = "0.3.30" +futures-util = "0.3.30" +rand = "0.8.5" +thiserror = "1.0.63" tokio.workspace = true +tokio-util = "0.7.11" tracing.workspace = true + +[dev-dependencies] +tracing-test = { version = "0.2.5", features = ["no-env-filter"] } diff --git a/crates/mad/src/lib.rs b/crates/mad/src/lib.rs index 8b13789..9b586ac 100644 --- a/crates/mad/src/lib.rs +++ b/crates/mad/src/lib.rs @@ -1 +1,209 @@ +use futures::stream::FuturesUnordered; +use futures_util::StreamExt; +use std::{fmt::Display, sync::Arc}; +use tokio_util::sync::CancellationToken; + +#[derive(thiserror::Error, Debug)] +pub enum MadError { + #[error("component failed: {0}")] + Inner(#[source] anyhow::Error), + + #[error("component(s) failed: {run}")] + RunError { run: anyhow::Error }, + + #[error("component(s) failed: {close}")] + CloseError { close: anyhow::Error }, + + #[error("component(s) failed: {0}")] + AggregateError(AggregateError), + + #[error("setup not defined")] + SetupNotDefined, + + #[error("close not defined")] + CloseNotDefined, +} + +#[derive(Debug)] +pub struct AggregateError { + errors: Vec, +} + +impl Display for AggregateError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("MadError::AggregateError: (")?; + + for error in &self.errors { + f.write_str(&error.to_string())?; + f.write_str(", ")?; + } + + f.write_str(")") + } +} + +pub struct Mad { + components: Vec>, + + should_cancel: Option, +} + +impl Mad { + pub fn builder() -> Self { + Self { + components: Vec::default(), + + should_cancel: Some(std::time::Duration::from_millis(100)), + } + } + + pub fn add(&mut self, component: impl IntoComponent) -> &mut Self { + self.components.push(component.into_component()); + + self + } + + pub fn cancellation(&mut self, should_cancel: Option) -> &mut Self { + self.should_cancel = should_cancel; + + self + } + + pub async fn run(&mut self) -> Result<(), MadError> { + tracing::info!("running mad setup"); + + self.setup_components().await?; + + let run_result = self.run_components().await; + + let close_result = self.close_components().await; + + match (run_result, close_result) { + (Err(run), Err(close)) => { + return Err(MadError::AggregateError(AggregateError { + errors: vec![run, close], + })) + } + (Ok(_), Ok(_)) => {} + (Ok(_), Err(close)) => return Err(close), + (Err(run), Ok(_)) => return Err(run), + } + + Ok(()) + } + + async fn setup_components(&mut self) -> Result<(), MadError> { + tracing::debug!("setting up components"); + + for comp in &self.components { + tracing::trace!(component = &comp.name(), "mad setting up"); + + match comp.setup().await { + Ok(_) | Err(MadError::SetupNotDefined) => {} + Err(e) => return Err(e), + }; + } + + tracing::debug!("finished setting up components"); + + Ok(()) + } + + async fn run_components(&mut self) -> Result<(), MadError> { + tracing::debug!("running components"); + + let mut channels = Vec::new(); + let cancellation_token = CancellationToken::new(); + let job_cancellation = CancellationToken::new(); + + for comp in &self.components { + let comp = comp.clone(); + let cancellation_token = cancellation_token.child_token(); + let job_cancellation = job_cancellation.child_token(); + + let (error_tx, error_rx) = tokio::sync::mpsc::channel::>(1); + channels.push(error_rx); + + tokio::spawn(async move { + tracing::debug!(component = &comp.name(), "mad running"); + + tokio::select! { + _ = cancellation_token.cancelled() => { + error_tx.send(Ok(())).await + } + res = comp.run(job_cancellation) => { + error_tx.send(res).await + } + } + }); + } + + let mut futures = FuturesUnordered::new(); + for channel in channels.iter_mut() { + futures.push(channel.recv()); + } + + while let Some(Some(msg)) = futures.next().await { + tracing::trace!("received end signal from a component"); + + if let Err(e) = msg { + tracing::debug!(error = e.to_string(), "stopping running components"); + job_cancellation.cancel(); + + if let Some(cancel_wait) = self.should_cancel { + tokio::time::sleep(cancel_wait).await; + + cancellation_token.cancel(); + } + } + } + + tracing::debug!("ran components"); + + Ok(()) + } + + async fn close_components(&mut self) -> Result<(), MadError> { + tracing::debug!("closing components"); + + for comp in &self.components { + tracing::trace!(component = &comp.name(), "mad closing"); + match comp.close().await { + Ok(_) | Err(MadError::CloseNotDefined) => {} + Err(e) => return Err(e), + }; + } + + tracing::debug!("closed components"); + + Ok(()) + } +} + +#[async_trait::async_trait] +pub trait Component { + fn name(&self) -> Option { + None + } + + async fn setup(&self) -> Result<(), MadError> { + Err(MadError::SetupNotDefined) + } + + async fn run(&self, cancellation_token: CancellationToken) -> Result<(), MadError>; + + async fn close(&self) -> Result<(), MadError> { + Err(MadError::CloseNotDefined) + } +} + +pub trait IntoComponent { + fn into_component(self) -> Arc; +} + +impl IntoComponent for T { + fn into_component(self) -> Arc { + Arc::new(self) + } +} diff --git a/crates/mad/tests/mod.rs b/crates/mad/tests/mod.rs new file mode 100644 index 0000000..d7eafde --- /dev/null +++ b/crates/mad/tests/mod.rs @@ -0,0 +1,93 @@ +use anyhow::anyhow; +use async_trait::async_trait; +use mad::{Component, Mad}; +use rand::Rng; +use tokio_util::sync::CancellationToken; +use tracing_test::traced_test; + +struct NeverEndingRun {} + +#[async_trait] +impl Component for NeverEndingRun { + fn name(&self) -> Option { + Some("NeverEndingRun".into()) + } + + async fn run(&self, cancellation: CancellationToken) -> Result<(), mad::MadError> { + let millis_wait = rand::thread_rng().gen_range(50..1000); + + tokio::time::sleep(std::time::Duration::from_millis(millis_wait)).await; + + return Err(mad::MadError::Inner(anyhow!("failed to run stuff"))); + + Ok(()) + } +} + +#[tokio::test] +#[traced_test] +async fn test_can_run_components() -> anyhow::Result<()> { + Mad::builder() + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .add(NeverEndingRun {}) + .run() + .await?; + + anyhow::bail!("stuff"); + + Ok(()) +}