├── .gitignore ├── src ├── prelude.rs ├── channel │ ├── async_std.rs │ ├── lifeline.rs │ ├── tokio.rs │ └── postage.rs ├── request.rs ├── channel.rs ├── dyn_bus │ ├── slot.rs │ ├── macros.rs │ └── storage.rs ├── dyn_bus.rs ├── test.rs ├── storage.rs ├── error.rs ├── spawn.rs ├── bus.rs ├── lib.rs └── service.rs ├── .github └── workflows │ ├── release.yml │ └── rust.yml ├── LICENSE ├── Cargo.toml ├── examples ├── associated.rs ├── async-std.rs ├── shutdown.rs ├── impl_channel.rs ├── carrier.rs ├── hello.rs └── state.rs ├── CODE_OF_CONDUCT.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | .vscode/settings.json 4 | -------------------------------------------------------------------------------- /src/prelude.rs: -------------------------------------------------------------------------------- 1 | //! Prelude, including all the traits and types required for typical lifeline usage. 2 | 3 | pub use crate::{Bus, CarryFrom, CarryInto, Lifeline, Message, Resource, Service, Task}; 4 | 5 | #[cfg(feature = "dyn-bus")] 6 | pub use crate::lifeline_bus; 7 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | crates_io: 8 | name: crates.io publish 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: toolchain 15 | uses: actions-rs/toolchain@v1 16 | with: 17 | toolchain: stable 18 | profile: minimal 19 | override: true 20 | 21 | - name: cargo publish 22 | run: cargo publish --token ${{ secrets.CRATES_IO_TOKEN }} 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Austin Jones 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/channel/async_std.rs: -------------------------------------------------------------------------------- 1 | use super::Channel; 2 | use crate::error::SendError as LifelineSendError; 3 | use crate::{impl_channel_clone, impl_channel_take}; 4 | use async_std::channel::{bounded, Receiver, Sender}; 5 | use async_trait::async_trait; 6 | use std::fmt::Debug; 7 | 8 | impl Channel for Sender { 9 | type Tx = Self; 10 | type Rx = Receiver; 11 | 12 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 13 | bounded(capacity) 14 | } 15 | 16 | fn default_capacity() -> usize { 17 | 16 18 | } 19 | } 20 | 21 | impl_channel_clone!(Sender); 22 | impl_channel_take!(Receiver); 23 | 24 | #[async_trait] 25 | impl crate::Sender for Sender 26 | where 27 | T: Debug + Send, 28 | { 29 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 30 | Sender::send(self, value) 31 | .await 32 | .map_err(|err| LifelineSendError::Return(err.0))?; 33 | 34 | Ok(()) 35 | } 36 | } 37 | 38 | #[async_trait] 39 | impl crate::Receiver for Receiver 40 | where 41 | T: Debug + Send, 42 | { 43 | async fn recv(&mut self) -> Option { 44 | Receiver::recv(self).await.ok() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/channel/lifeline.rs: -------------------------------------------------------------------------------- 1 | use crate::error::SendError; 2 | use async_trait::async_trait; 3 | use std::fmt::Debug; 4 | 5 | impl SendError { 6 | pub fn take_message(self) -> Option { 7 | match self { 8 | SendError::Return(value) => Some(value), 9 | SendError::Closed => None, 10 | } 11 | } 12 | } 13 | 14 | /// The sender half of an asynchronous channel, which may be bounded/unbounded, mpsc/broadcast/oneshot, etc. 15 | /// 16 | /// This trait provides a consistent interface for all async senders, which makes your app code 17 | /// very robust to channel changes on the bus. It also allows `impl Sender` in your associated function signatures. 18 | #[async_trait] 19 | pub trait Sender { 20 | async fn send(&mut self, value: T) -> Result<(), SendError>; 21 | } 22 | 23 | /// The receiver half of an asynchronous channel, which may be bounded/unbounded, mpsc/broadcast/oneshot, etc. 24 | /// 25 | /// This trait provides a consistent interface for all async receivers, which makes your app code 26 | /// very robust to channel changes on the bus. It also allows `impl Receiver` in your associated function signatures. 27 | #[async_trait] 28 | pub trait Receiver { 29 | async fn recv(&mut self) -> Option; 30 | } 31 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | //! A request/response helper type, which can be sent over messages. 2 | 3 | use std::future::Future; 4 | use tokio::sync::oneshot; 5 | 6 | /// If you need synchronous RPC, you can use this utility 7 | /// ```rust 8 | /// use lifeline::request::Request; 9 | /// 10 | /// struct Send(usize); 11 | /// #[derive(Debug, Eq, PartialEq)] 12 | /// struct Recv(usize); 13 | /// 14 | /// lifeline::test::block_on(async { 15 | /// let (request, mut recv) = Request::send(Send(42)); 16 | /// 17 | /// // send the request along a channel, and in a service: 18 | /// request.reply(|send| async move { Recv(send.0) }).await; 19 | /// 20 | /// let resp = recv.await; 21 | /// assert_eq!(Ok(Recv(42)), resp); 22 | /// }) 23 | /// ``` 24 | pub struct Request { 25 | send: Send, 26 | recv: oneshot::Sender, 27 | } 28 | 29 | impl Request { 30 | /// Constructs a pair of Request, and Receiver for the response 31 | pub fn send(send: Send) -> (Self, oneshot::Receiver) { 32 | let (tx, rx) = oneshot::channel(); 33 | let request = Self { send, recv: tx }; 34 | (request, rx) 35 | } 36 | 37 | /// Asynchronously replies to the given request, using the provided closure 38 | pub async fn reply(self, respond: Fn) -> Result<(), Recv> 39 | where 40 | Fn: FnOnce(Send) -> Fut, 41 | Fut: Future, 42 | { 43 | let response = respond(self.send).await; 44 | self.recv.send(response) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lifeline" 3 | version = "0.6.1" 4 | description = "Lifeline is a dependency injection library for asynchronous message-based applications." 5 | keywords = ["async", "tokio", "async", "actor", "actors"] 6 | categories = ["asynchronous", "rust-patterns", "web-programming"] 7 | readme = "README.md" 8 | authors = ["Austin Jones "] 9 | documentation = "https://docs.rs/lifeline/" 10 | homepage = "https://github.com/austinjones/lifeline-rs" 11 | repository = "https://github.com/austinjones/lifeline-rs" 12 | edition = "2018" 13 | license = "MIT" 14 | 15 | [badges] 16 | maintenance = { status = "actively-developed" } 17 | 18 | [dependencies] 19 | postage = { version = "0.4", optional = true } 20 | pin-project = "0.4.23" 21 | futures-util = { version = "0.3", default-features = false } 22 | 23 | async-trait = "0.1" 24 | thiserror = "1.0" 25 | anyhow = "1.0" 26 | 27 | log = "0.4" 28 | regex = "1.3" 29 | 30 | tokio = { version = "1.0", default-features = false, optional = true } 31 | async-std = { version = "1.9", default-features = false, optional = true } 32 | 33 | [dev-dependencies] 34 | anyhow = "1.0" 35 | simple_logger = "1.9" 36 | tokio = { version = "1.0", features = ["sync", "time", "macros", "rt-multi-thread"] } 37 | 38 | [features] 39 | default = ["dyn-bus", "tokio-executor", "tokio-channels", "postage-channels"] 40 | 41 | dyn-bus = [] 42 | 43 | tokio-executor = ["tokio/rt"] 44 | tokio-channels = ["tokio/sync"] 45 | 46 | async-std-executor = ["async-std/default"] 47 | async-std-channels = ["async-std/unstable"] 48 | async-std-attributes = ["async-std/attributes"] 49 | 50 | postage-channels = ["postage"] 51 | 52 | subscription-channel = [] 53 | 54 | [[example]] 55 | name = "async-std" 56 | required-features = ["dyn-bus", "async-std-executor", "async-std-channels"] -------------------------------------------------------------------------------- /src/channel.rs: -------------------------------------------------------------------------------- 1 | use crate::Storage; 2 | 3 | #[cfg(feature = "async-std-channels")] 4 | mod async_std; 5 | 6 | pub mod lifeline; 7 | 8 | #[cfg(feature = "tokio-channels")] 9 | mod tokio; 10 | 11 | #[cfg(feature = "postage-channels")] 12 | mod postage; 13 | 14 | /// A channel's (Sender, Receiver) pair. Defines how the bus constructs and retrieves the values. 15 | /// 16 | /// Channel endpoints can either be taken, or cloned. The `Channel` trait has default implementations that honor the 17 | /// `Storage` trait implementation of channel endpoints. However, in some cases (such as tokio broadcast channels) the tx & rx endpoints are both required to implement this trait. 18 | pub trait Channel { 19 | /// The Sender half of the channel. This is used in `Message` implementations to attach channels to a `Bus`. 20 | type Tx: Storage + Send + 'static; 21 | 22 | /// The Receiver half of the channel. This is constructed when `bus.tx` or `bus.rx` is called, and is driven by the `Message` implementation for the message. 23 | type Rx: Storage + Send + 'static; 24 | 25 | /// Constructs a new `(Sender, Receiver)` pair. If the channel is bounded, use the provided capacity. 26 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx); 27 | 28 | /// If the channel is bounded, provide a default capacity hint. Users can override this with `bus.capacity(usize)` 29 | fn default_capacity() -> usize; 30 | 31 | /// Clones the Self::Tx value, or takes it from the option if this endpoint is not cloneable. 32 | fn clone_tx(tx: &mut Option) -> Option { 33 | Self::Tx::take_or_clone(tx) 34 | } 35 | 36 | /// Clones the Self::Rx value, or takes it from the option if this endpoint is not cloneable. 37 | /// The Tx endpoint is also available, which is necessary to implement Channel for broadcast channels 38 | /// (where new receivers are created from a tx subscription call) 39 | fn clone_rx(rx: &mut Option, _tx: Option<&Self::Tx>) -> Option { 40 | Self::Rx::take_or_clone(rx) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/associated.rs: -------------------------------------------------------------------------------- 1 | use bus::ExampleBus; 2 | use lifeline::prelude::*; 3 | use message::*; 4 | use postage::{sink::Sink, stream::Stream}; 5 | use service::ExampleService; 6 | 7 | /// If a service spawns many tasks, it helps to break the run functions up. 8 | /// You can do this by defining associated functions on the service type, 9 | /// and calliing them with Self::run_something(rx, tx) 10 | #[tokio::main] 11 | pub async fn main() -> anyhow::Result<()> { 12 | let bus = ExampleBus::default(); 13 | let _service = ExampleService::spawn(&bus)?; 14 | 15 | let mut rx = bus.rx::()?; 16 | let mut tx = bus.tx::()?; 17 | 18 | tx.send(ExampleRecv::Hello).await?; 19 | 20 | let oh_hello = rx.recv().await; 21 | assert_eq!(Some(ExampleSend::OhHello), oh_hello); 22 | println!("Service says {:?}", oh_hello.unwrap()); 23 | 24 | Ok(()) 25 | } 26 | 27 | mod bus { 28 | use crate::message::*; 29 | use lifeline::prelude::*; 30 | use postage::broadcast; 31 | 32 | lifeline_bus!(pub struct ExampleBus); 33 | 34 | impl Message for ExampleRecv { 35 | type Channel = broadcast::Sender; 36 | } 37 | 38 | impl Message for ExampleSend { 39 | type Channel = broadcast::Sender; 40 | } 41 | } 42 | 43 | mod message { 44 | #[derive(Debug, Clone)] 45 | pub enum ExampleRecv { 46 | Hello, 47 | } 48 | 49 | #[derive(Debug, Clone, Eq, PartialEq)] 50 | pub enum ExampleSend { 51 | OhHello, 52 | } 53 | } 54 | 55 | mod service { 56 | use crate::bus::ExampleBus; 57 | use crate::message::*; 58 | use lifeline::prelude::*; 59 | use lifeline::{Receiver, Sender}; 60 | 61 | pub struct ExampleService { 62 | _greet: Lifeline, 63 | } 64 | 65 | impl Service for ExampleService { 66 | type Bus = ExampleBus; 67 | type Lifeline = anyhow::Result; 68 | 69 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 70 | let rx = bus.rx::()?; 71 | let tx = bus.tx::()?; 72 | 73 | let _greet = Self::try_task("greet", Self::run_greet(rx, tx)); 74 | 75 | Ok(Self { _greet }) 76 | } 77 | } 78 | 79 | impl ExampleService { 80 | async fn run_greet( 81 | mut rx: impl Receiver, 82 | mut tx: impl Sender, 83 | ) -> anyhow::Result<()> { 84 | while let Some(recv) = rx.recv().await { 85 | match recv { 86 | ExampleRecv::Hello => { 87 | tx.send(ExampleSend::OhHello).await?; 88 | } 89 | } 90 | } 91 | 92 | Ok(()) 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/dyn_bus/slot.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::type_name, Channel, Storage}; 2 | 3 | use std::{any::Any, fmt::Debug}; 4 | 5 | pub(crate) struct BusSlot { 6 | name: String, 7 | value: Option>, 8 | } 9 | 10 | impl Debug for BusSlot { 11 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 12 | let string = match self.value { 13 | Some(_) => format!("BusSlot<{}>::Some(_)", self.name.as_str()), 14 | None => format!("BusSlot<{}>::Empty", self.name.as_str()), 15 | }; 16 | 17 | f.debug_struct(string.as_str()).finish() 18 | } 19 | } 20 | 21 | impl BusSlot { 22 | pub fn new(value: Option) -> Self { 23 | Self { 24 | // TODO: think about this? uses memory, but it's nice for debugging 25 | name: type_name::(), 26 | value: value.map(|v| Box::new(v) as Box), 27 | } 28 | } 29 | 30 | pub fn empty() -> Self { 31 | Self { 32 | name: type_name::(), 33 | value: None, 34 | } 35 | } 36 | 37 | pub fn put(&mut self, value: T) { 38 | self.value = Some(Box::new(value)) 39 | } 40 | 41 | pub fn get_tx(&self) -> Option<&Chan::Tx> 42 | where 43 | Chan: Channel, 44 | Chan::Tx: Any + 'static, 45 | { 46 | self.value 47 | .as_ref() 48 | .map(|boxed| boxed.downcast_ref().unwrap()) 49 | } 50 | 51 | pub fn clone_rx(&mut self, tx: Option<&Chan::Tx>) -> Option 52 | where 53 | Chan: Channel, 54 | Chan::Rx: Storage + Send + 'static, 55 | { 56 | let mut taken = self.value.take().map(Self::cast); 57 | let cloned = Chan::clone_rx(&mut taken, tx); 58 | self.value = taken.map(|value| Box::new(value) as Box); 59 | cloned 60 | } 61 | 62 | pub fn clone_tx(&mut self) -> Option 63 | where 64 | Chan: Channel, 65 | Chan::Tx: Storage + Send + 'static, 66 | { 67 | let mut taken = self.value.take().map(Self::cast); 68 | let cloned = Chan::clone_tx(&mut taken); 69 | self.value = taken.map(|value| Box::new(value) as Box); 70 | cloned 71 | } 72 | 73 | pub fn clone_storage(&mut self) -> Option 74 | where 75 | Res: Storage + Send + Any, 76 | { 77 | let mut taken = self.value.take().map(Self::cast); 78 | let cloned = Res::take_or_clone(&mut taken); 79 | self.value = taken.map(|value| Box::new(value) as Box); 80 | cloned 81 | } 82 | 83 | fn cast(boxed: Box) -> T { 84 | *boxed 85 | .downcast::() 86 | .expect("BusSlot should always have correct type") 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/dyn_bus.rs: -------------------------------------------------------------------------------- 1 | //! The DynBus implementation used by `lifeline_bus!`, and TypeId-based slot storage. 2 | mod macros; 3 | mod slot; 4 | mod storage; 5 | 6 | use crate::{ 7 | bus::{Message, Resource}, 8 | error::{AlreadyLinkedError, TakeChannelError, TakeResourceError}, 9 | Bus, Channel, 10 | }; 11 | 12 | pub use storage::DynBusStorage; 13 | 14 | /// An extension trait which defines operations on a DynBus, which stores `box dyn` trait objects internally. 15 | /// 16 | /// DynBus implementations are created using the `lifeline_bus!` macro. 17 | pub trait DynBus: Bus { 18 | /// Stores an manually constructed Receiver on the bus, for the provided message type. 19 | /// 20 | /// This is useful if you need to link a lifeline bus to other async message-based code. 21 | /// 22 | /// If the message channel has already been linked (from a call to `bus.rx`, `bus.tx`, or `bus.capacity`), returns an error. 23 | fn store_rx(&self, rx: ::Rx) -> Result<(), AlreadyLinkedError> 24 | where 25 | Msg: Message + 'static; 26 | 27 | /// Stores an manually constructed Sender on the bus, for the provided message type. 28 | /// 29 | /// This is useful if you need to link a lifeline bus to other async message-based code. 30 | /// 31 | /// If the message channel has already been linked (from a call to `bus.rx`, `bus.tx`, or `bus.capacity`), returns an error. 32 | fn store_tx(&self, tx: ::Tx) -> Result<(), AlreadyLinkedError> 33 | where 34 | Msg: Message + 'static; 35 | 36 | /// Stores a channel pair on the bus, for the provided message type. 37 | /// 38 | /// If the message channel has already been linked (from a call to `bus.rx`, `bus.tx`, or `bus.capacity`), returns an error. 39 | fn store_channel( 40 | &self, 41 | rx: ::Rx, 42 | tx: ::Tx, 43 | ) -> Result<(), AlreadyLinkedError> 44 | where 45 | Msg: Message + 'static; 46 | 47 | /// Stores a resource on the bus. 48 | /// 49 | /// Resources are commonly used for clonable configuration structs, or takeable resources such as websocket connections. 50 | fn store_resource>(&self, resource: R); 51 | 52 | /// Returns the `DynBusStorage` struct which manages the trait object slots. 53 | fn storage(&self) -> &DynBusStorage; 54 | } 55 | 56 | impl Bus for T 57 | where 58 | T: DynBus, 59 | { 60 | fn rx(&self) -> Result<::Rx, TakeChannelError> 61 | where 62 | Msg: crate::bus::Message + 'static, 63 | { 64 | self.storage().link_channel::(); 65 | let rx = self.storage().clone_rx::()?; 66 | Ok(rx) 67 | } 68 | 69 | fn tx(&self) -> Result<::Tx, TakeChannelError> 70 | where 71 | Msg: crate::bus::Message + 'static, 72 | { 73 | self.storage().link_channel::(); 74 | let tx = self.storage().clone_tx::()?; 75 | Ok(tx) 76 | } 77 | 78 | fn capacity(&self, capacity: usize) -> Result<(), AlreadyLinkedError> 79 | where 80 | Msg: Message + 'static, 81 | { 82 | self.storage().capacity::(capacity) 83 | } 84 | 85 | fn resource(&self) -> Result 86 | where 87 | Res: Resource, 88 | { 89 | self.storage().clone_resource::() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at implAustin@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /examples/async-std.rs: -------------------------------------------------------------------------------- 1 | //! A copy of the hello.rs example, using the async_std runtime 2 | //! 3 | //! Can be run using: `cargo run --example async-std --no-default-features --features="dyn-bus async-std-executor async-std-channels"` 4 | 5 | use bus::ExampleBus; 6 | use lifeline::prelude::*; 7 | use message::{ExampleRecv, ExampleSend}; 8 | use service::ExampleService; 9 | use std::time::Duration; 10 | 11 | #[async_std::main] 12 | pub async fn main() -> anyhow::Result<()> { 13 | let bus = ExampleBus::default(); 14 | let _service = ExampleService::spawn(&bus)?; 15 | 16 | let rx = bus.rx::()?; 17 | let tx = bus.tx::()?; 18 | 19 | drop(bus); 20 | 21 | tx.send(ExampleRecv::Hello).await?; 22 | tx.send(ExampleRecv::Goodbye).await?; 23 | 24 | let oh_hello = rx.recv().await; 25 | assert_eq!(Ok(ExampleSend::OhHello), oh_hello); 26 | println!("Service says {:?}", oh_hello.unwrap()); 27 | 28 | let aww_ok = rx.recv().await; 29 | assert_eq!(Ok(ExampleSend::AwwOk), aww_ok); 30 | println!("Service says {:?}", aww_ok.unwrap()); 31 | 32 | println!("All done."); 33 | 34 | // For some reason, async_std panics due to printlns on shutdown 35 | async_std::task::sleep(Duration::from_millis(100)).await; 36 | 37 | Ok(()) 38 | } 39 | 40 | /// These are the messages which our application uses to communicate. 41 | mod message { 42 | #[derive(Debug, Clone, Eq, PartialEq)] 43 | pub enum ExampleSend { 44 | OhHello, 45 | AwwOk, 46 | } 47 | 48 | #[derive(Debug, Clone)] 49 | pub enum ExampleRecv { 50 | Hello, 51 | Goodbye, 52 | } 53 | } 54 | 55 | /// This is the lifeline bus. 56 | /// The bus carries channels (senders/receivers). 57 | mod bus { 58 | use crate::message::{ExampleRecv, ExampleSend}; 59 | use lifeline::{lifeline_bus, Message}; 60 | 61 | // This is a macro that generates an ExampleBus struct, 62 | // and implements DynBus for it. 63 | lifeline_bus!(pub struct ExampleBus); 64 | 65 | impl Message for ExampleSend { 66 | type Channel = async_std::channel::Sender; 67 | } 68 | 69 | impl Message for ExampleRecv { 70 | type Channel = async_std::channel::Sender; 71 | } 72 | } 73 | 74 | /// This is the service. 75 | /// The service is a spawnable task that launches from the bus. 76 | mod service { 77 | use super::bus::ExampleBus; 78 | use crate::message::{ExampleRecv, ExampleSend}; 79 | use lifeline::prelude::*; 80 | 81 | pub struct ExampleService { 82 | _greet: Lifeline, 83 | } 84 | 85 | impl Service for ExampleService { 86 | type Bus = ExampleBus; 87 | type Lifeline = anyhow::Result; 88 | 89 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 90 | let rx = bus.rx::()?; 91 | let tx = bus.tx::()?; 92 | 93 | let _greet = Self::try_task("greet", async move { 94 | while let Ok(recv) = rx.recv().await { 95 | match recv { 96 | ExampleRecv::Hello => { 97 | tx.send(ExampleSend::OhHello).await?; 98 | } 99 | ExampleRecv::Goodbye => { 100 | tx.send(ExampleSend::AwwOk).await?; 101 | } 102 | } 103 | } 104 | 105 | Ok(()) 106 | }); 107 | 108 | Ok(Self { _greet }) 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | 6 | pull_request: 7 | 8 | name: Continuous Integration 9 | 10 | jobs: 11 | dependencies: 12 | name: cargo build | dependencies 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v2 17 | 18 | - id: cargo-cache 19 | name: cache 20 | uses: austinjones/rust-cache@v1 21 | with: 22 | key: ci 23 | 24 | - name: cargo build | dependencies 25 | uses: actions-rs/cargo@v1 26 | if: steps.cargo-cache.outputs.cache-hit != 'true' 27 | with: 28 | command: build 29 | args: --all-features 30 | 31 | - name: cargo build | dev dependencies 32 | uses: actions-rs/cargo@v1 33 | if: steps.cargo-cache.outputs.cache-hit != 'true' 34 | with: 35 | command: test 36 | args: --all-features --no-run 37 | 38 | check: 39 | name: cargo check 40 | needs: dependencies 41 | runs-on: ubuntu-latest 42 | steps: 43 | - name: checkout 44 | uses: actions/checkout@v2 45 | 46 | - id: cargo-cache 47 | name: cache 48 | uses: austinjones/rust-cache@v1 49 | with: 50 | key: ci 51 | 52 | - name: cargo check 53 | uses: actions-rs/cargo@v1 54 | with: 55 | command: check 56 | 57 | - name: cargo clippy 58 | uses: actions-rs/clippy-check@v1 59 | with: 60 | token: ${{ secrets.GITHUB_TOKEN }} 61 | 62 | - name: cargo fmt 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | test: 69 | name: cargo test 70 | needs: dependencies 71 | runs-on: ubuntu-latest 72 | steps: 73 | - name: checkout 74 | uses: actions/checkout@v2 75 | 76 | - id: cargo-cache 77 | name: cache 78 | uses: austinjones/rust-cache@v1 79 | with: 80 | key: ci 81 | 82 | - uses: actions-rs/cargo@v1 83 | with: 84 | command: test 85 | args: --all-features 86 | 87 | # Run all the examples 88 | - name: example | associated 89 | uses: actions-rs/cargo@v1 90 | with: 91 | command: run 92 | args: --example associated 93 | 94 | # Run the async-std example with custom features 95 | - name: example | async-std 96 | uses: actions-rs/cargo@v1 97 | with: 98 | command: run 99 | args: --example async-std --no-default-features --features "dyn-bus async-std-executor async-std-channels async-std-attributes" 100 | 101 | - name: example | carrier 102 | uses: actions-rs/cargo@v1 103 | with: 104 | command: run 105 | args: --example carrier 106 | 107 | - name: example | hello 108 | uses: actions-rs/cargo@v1 109 | with: 110 | command: run 111 | args: --example hello 112 | 113 | - name: example | impl_channel 114 | uses: actions-rs/cargo@v1 115 | with: 116 | command: run 117 | args: --example impl_channel 118 | 119 | - name: example | shutdown 120 | uses: actions-rs/cargo@v1 121 | with: 122 | command: run 123 | args: --example shutdown 124 | 125 | - name: example | state 126 | uses: actions-rs/cargo@v1 127 | with: 128 | command: run 129 | args: --example state 130 | 131 | 132 | -------------------------------------------------------------------------------- /src/dyn_bus/macros.rs: -------------------------------------------------------------------------------- 1 | /// Defines a lifeline bus: it's struct, `Bus` impl, and `DynBus` impl. 2 | /// 3 | /// ## Examples 4 | /// ``` 5 | /// use lifeline::prelude::*; 6 | /// use tokio::sync::mpsc; 7 | /// 8 | /// lifeline_bus!(pub struct ExampleBus); 9 | /// 10 | /// // carry ExampleMessage on the bus, using a tokio mpsc sender. 11 | /// #[derive(Debug)] 12 | /// pub struct ExampleMessage {} 13 | /// impl Message for ExampleMessage { 14 | /// type Channel = mpsc::Sender; 15 | /// } 16 | /// ``` 17 | /// You can also define private structs: 18 | /// ``` 19 | /// use lifeline::prelude::*; 20 | /// 21 | /// lifeline_bus!(struct PrivateExampleBus); 22 | /// ``` 23 | /// You can also define generics (which are constrained to Debug): 24 | /// ``` 25 | /// use lifeline::prelude::*; 26 | /// lifeline_bus!(pub struct ExampleBus); 27 | /// ``` 28 | /// ## Prelude, auto-imports, and rust-analyzer 29 | /// Unfortunately, rust-analyzer doesn't handle auto-imports for structures defined in macros. 30 | /// There is an ergonomic solution: define a prelude module in your crate root, and `pub use` all your bus structs. 31 | /// If you want, you can `pub use lifeline::prelude::*` as well. 32 | #[macro_export] 33 | macro_rules! lifeline_bus ( 34 | (struct $name:ident $(< $( $gen:ident ),+ >)? ) => { 35 | lifeline_bus! { () struct $name $(< $( $gen ),+ >)? } 36 | }; 37 | 38 | (pub struct $name:ident $(< $( $gen:ident ),+ >)* ) => { 39 | lifeline_bus! { (pub) struct $name $(< $( $gen ),+ >)* } 40 | }; 41 | 42 | (($($vis:tt)*) struct $name:ident $(< $( $gen:ident ),+ >)? ) => { 43 | #[derive(Debug)] 44 | #[allow(non_snake_case)] 45 | $($vis)* struct $name $(< $( $gen: std::fmt::Debug ),+ >)? { 46 | storage: $crate::dyn_bus::DynBusStorage, 47 | $( 48 | $( $gen: std::marker::PhantomData<$gen> ),+ 49 | )? 50 | } 51 | 52 | impl$(< $( $gen: std::fmt::Debug ),+ >)? std::default::Default for $name $(< $( $gen ),+ >)? { 53 | fn default() -> Self { 54 | Self { 55 | storage: $crate::dyn_bus::DynBusStorage::default(), 56 | $( 57 | $( $gen: std::marker::PhantomData::<$gen> ),+ 58 | )? 59 | } 60 | } 61 | } 62 | 63 | impl$(< $( $gen: std::fmt::Debug ),+ >)? $crate::dyn_bus::DynBus for $name$(< $( $gen ),+ >)? { 64 | fn store_rx(&self, rx: ::Rx) -> Result<(), $crate::error::AlreadyLinkedError> 65 | where Msg: $crate::Message + 'static 66 | { 67 | self.storage().store_channel::(Some(rx), None) 68 | } 69 | 70 | fn store_tx(&self, tx: ::Tx) -> Result<(), $crate::error::AlreadyLinkedError> 71 | where Msg: $crate::Message + 'static 72 | { 73 | self.storage().store_channel::(None, Some(tx)) 74 | } 75 | 76 | fn store_channel( 77 | &self, 78 | rx: ::Rx, 79 | tx: ::Tx 80 | ) -> Result<(), $crate::error::AlreadyLinkedError> 81 | where Msg: $crate::Message + 'static 82 | { 83 | self.storage().store_channel::(Some(rx), Some(tx)) 84 | } 85 | 86 | fn store_resource>(&self, resource: R) { 87 | self.storage.store_resource::(resource) 88 | } 89 | 90 | fn storage(&self) -> &$crate::dyn_bus::DynBusStorage { 91 | &self.storage 92 | } 93 | } 94 | } 95 | ); 96 | -------------------------------------------------------------------------------- /src/test.rs: -------------------------------------------------------------------------------- 1 | //! Helpers which assist in testing applications based on lifeline. 2 | 3 | /// Blocks on the future, using a new async runtime. 4 | /// This is helpful in doctests 5 | #[cfg(feature = "tokio-executor")] 6 | pub fn block_on, Out>(fut: Fut) -> Out { 7 | use tokio::runtime::Builder; 8 | 9 | let runtime = Builder::new_current_thread() 10 | .build() 11 | .expect("doctest runtime creation failed"); 12 | runtime.block_on(fut) 13 | } 14 | 15 | // forked from https://github.com/tokio-rs/tokio/pull/2522/files 16 | // thank you https://github.com/RadicalZephyr !! 17 | // this was just what Lifeline needs. 18 | 19 | /// Asserts that the expression completes within a given number of milliseconds. 20 | /// 21 | /// This will invoke the `panic!` macro if the provided future 22 | /// expression fails to complete within the given number of 23 | /// milliseconds. This macro expands to an `await` and must be 24 | /// invoked inside an async context. 25 | /// 26 | /// A default timeout of 50ms is used if no duration is passed. 27 | /// 28 | /// # Examples 29 | /// 30 | /// ```rust 31 | /// use lifeline::assert_completes; 32 | /// use tokio::time::sleep; 33 | /// 34 | /// # let fut = 35 | /// async { 36 | /// // Succeeds because default time is longer than delay. 37 | /// assert_completes!(sleep(Duration::from_millis(5))); 38 | /// } 39 | /// # ; 40 | /// # let mut runtime = tokio::runtime::Runtime::new().unwrap(); 41 | /// # runtime.block_on(fut); 42 | ///``` 43 | /// 44 | /// ```rust,should_panic 45 | /// use lifeline::assert_completes; 46 | /// use tokio::time::sleep; 47 | /// 48 | /// # let fut = 49 | /// async { 50 | /// // Fails because timeout is shorter than delay. 51 | /// assert_completes!(sleep(Duration::from_millis(250)), 10); 52 | /// } 53 | /// # ; 54 | /// # let mut runtime = tokio::runtime::Runtime::new().unwrap(); 55 | /// # runtime.block_on(fut); 56 | /// ``` 57 | #[macro_export] 58 | macro_rules! assert_completes { 59 | ($e:expr) => { 60 | $crate::assert_completes!($e, 50) 61 | }; 62 | ($e:expr, $time:literal) => {{ 63 | use std::time::Duration; 64 | use tokio::time::timeout; 65 | match timeout(Duration::from_millis($time), $e).await { 66 | Ok(ret) => ret, 67 | Err(_) => panic!( 68 | "assertion failed: {} timed out after {} ms", 69 | stringify!($e), 70 | $time, 71 | ), 72 | } 73 | }}; 74 | } 75 | 76 | /// Asserts that the expression does not complete within a given number of milliseconds. 77 | /// 78 | /// This will invoke the `panic!` macro if the provided future 79 | /// expression completes within the given number of milliseconds. 80 | /// This macro expands to an `await` and must be invoked inside an 81 | /// async context. 82 | /// 83 | ///A default timeout of 50ms is used if no duration is passed. 84 | /// 85 | /// # Examples 86 | /// 87 | /// ```rust,should_panic 88 | /// use lifeline::assert_times_out; 89 | /// use tokio::time::sleep; 90 | /// 91 | /// # let fut = 92 | /// async { 93 | /// // Fails because default time is longer than delay. 94 | /// assert_times_out!(sleep(Duration::from_millis(5))); 95 | /// } 96 | /// # ; 97 | /// # let mut runtime = tokio::runtime::Runtime::new().unwrap(); 98 | /// # runtime.block_on(fut); 99 | /// ``` 100 | /// 101 | /// ```rust 102 | /// use lifeline::assert_times_out; 103 | /// use tokio::time::sleep; 104 | /// 105 | /// # let fut = 106 | /// async { 107 | /// // Succeeds because timeout is shorter than delay. 108 | /// assert_times_out!(sleep(Duration::from_millis(250)), 10); 109 | /// } 110 | /// # ; 111 | /// # let mut runtime = tokio::runtime::Runtime::new().unwrap(); 112 | /// # runtime.block_on(fut); 113 | /// ``` 114 | 115 | #[macro_export] 116 | macro_rules! assert_times_out { 117 | ($e:expr) => { 118 | $crate::assert_times_out!($e, 50) 119 | }; 120 | ($e:expr, $time:literal) => {{ 121 | use std::time::Duration; 122 | use tokio::time::timeout; 123 | match timeout(Duration::from_millis($time), $e).await { 124 | Ok(_) => panic!( 125 | "assertion failed: {} completed within {} ms", 126 | stringify!($e), 127 | $time, 128 | ), 129 | Err(err) => err, 130 | } 131 | }}; 132 | } 133 | -------------------------------------------------------------------------------- /src/storage.rs: -------------------------------------------------------------------------------- 1 | /// Defines a resource (or channel endpoint) which can be stored on the bus, and how it is taken or cloned. 2 | /// 3 | /// Lifeline provides helper macros which can implement the Clone or Take operations: 4 | /// [impl_storage_take!(Struct)](./macro.impl_storage_take.html), and [impl_storage_clone!(Struct)](./macro.impl_storage_clone.html) 5 | /// 6 | /// ## Resource Example 7 | /// ``` 8 | /// use lifeline::impl_storage_clone; 9 | /// 10 | /// #[derive(Debug, Clone)] 11 | /// struct ExampleConfiguration { 12 | /// value: bool 13 | /// } 14 | /// 15 | /// impl_storage_clone!(ExampleConfiguration); 16 | /// ``` 17 | /// 18 | /// ## Channel Example 19 | /// Lifeline also provides [impl_channel_take!(Struct\)](./macro.impl_channel_take.html) and [impl_channel_clone!(Struct\)](./macro.impl_channel_take.html), 20 | /// which configure the generic bounds required for a channel implementation: 21 | /// ``` 22 | /// use lifeline::impl_channel_take; 23 | /// use std::marker::PhantomData; 24 | /// 25 | /// #[derive(Debug)] 26 | /// struct ExampleSender { 27 | /// _t: PhantomData 28 | /// } 29 | /// 30 | /// impl_channel_take!(ExampleSender); 31 | /// ``` 32 | pub trait Storage: Sized + 'static { 33 | /// If Self::Tx implements clone, clone it. Otherwise use Option::take 34 | fn take_or_clone(res: &mut Option) -> Option; 35 | 36 | /// Helper which clones the provided option, requiring that Self: Clone 37 | fn clone_slot(res: &mut Option) -> Option 38 | where 39 | Self: Clone, 40 | { 41 | res.as_ref().map(|t| t.clone()) 42 | } 43 | 44 | /// Helper which takes the provided option, returning None if it's already been taken 45 | fn take_slot(res: &mut Option) -> Option { 46 | res.take() 47 | } 48 | } 49 | 50 | /// Specifies that this resource is taken, and is !Clone. 51 | /// ## Example: 52 | /// ``` 53 | /// use lifeline::impl_storage_take; 54 | /// 55 | /// struct ExampleResource {} 56 | /// 57 | /// impl_storage_take!(ExampleResource); 58 | /// ``` 59 | #[macro_export] 60 | macro_rules! impl_storage_take { 61 | ( $name:ty ) => { 62 | impl $crate::Storage for $name { 63 | fn take_or_clone(res: &mut Option) -> Option { 64 | Self::take_slot(res) 65 | } 66 | } 67 | }; 68 | } 69 | 70 | /// Specifies that this channel endpoint (Sender or Receiver) is taken. Provides a generic type T with the bounds required for the implementation. 71 | /// ## Example: 72 | /// ``` 73 | /// use std::marker::PhantomData; 74 | /// use lifeline::impl_channel_take; 75 | /// 76 | /// struct ExampleReceiver { 77 | /// _t: PhantomData 78 | /// } 79 | /// 80 | /// impl_channel_take!(ExampleReceiver); 81 | /// ``` 82 | #[macro_export] 83 | macro_rules! impl_channel_take { 84 | ( $name:ty ) => { 85 | impl $crate::Storage for $name { 86 | fn take_or_clone(res: &mut Option) -> Option { 87 | Self::take_slot(res) 88 | } 89 | } 90 | }; 91 | } 92 | 93 | /// Specifies that this resource is cloned. 94 | /// ## Example: 95 | /// ``` 96 | /// use lifeline::impl_storage_clone; 97 | /// 98 | /// #[derive(Clone)] 99 | /// struct ExampleResource {} 100 | /// 101 | /// impl_storage_clone!(ExampleResource); 102 | /// ``` 103 | #[macro_export] 104 | macro_rules! impl_storage_clone { 105 | ( $name:ty ) => { 106 | impl $crate::Storage for $name { 107 | fn take_or_clone(res: &mut Option) -> Option { 108 | Self::clone_slot(res) 109 | } 110 | } 111 | }; 112 | } 113 | 114 | /// Specifies that this channel endpoint (Sender or Receiver) is clonable. Provides a generic type T with the bounds required for the implementation. 115 | /// ## Example: 116 | /// ``` 117 | /// use std::marker::PhantomData; 118 | /// use lifeline::impl_channel_clone; 119 | /// 120 | /// struct ExampleReceiver { 121 | /// _t: PhantomData 122 | /// } 123 | /// 124 | /// impl Clone for ExampleReceiver { 125 | /// fn clone(&self) -> Self { 126 | /// Self { _t: PhantomData } 127 | /// } 128 | /// } 129 | /// 130 | /// impl_channel_clone!(ExampleReceiver); 131 | /// ``` 132 | #[macro_export] 133 | macro_rules! impl_channel_clone { 134 | ( $name:ty ) => { 135 | impl $crate::Storage for $name { 136 | fn take_or_clone(res: &mut Option) -> Option { 137 | Self::clone_slot(res) 138 | } 139 | } 140 | }; 141 | } 142 | -------------------------------------------------------------------------------- /src/channel/tokio.rs: -------------------------------------------------------------------------------- 1 | use super::Channel; 2 | use crate::error::SendError as LifelineSendError; 3 | use crate::{error::type_name, impl_channel_clone, impl_channel_take}; 4 | use async_trait::async_trait; 5 | use log::debug; 6 | use std::fmt::Debug; 7 | use tokio::sync::{broadcast, mpsc, oneshot, watch}; 8 | 9 | impl Channel for mpsc::Sender { 10 | type Tx = Self; 11 | type Rx = mpsc::Receiver; 12 | 13 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 14 | mpsc::channel(capacity) 15 | } 16 | 17 | fn default_capacity() -> usize { 18 | 16 19 | } 20 | } 21 | 22 | impl_channel_clone!(mpsc::Sender); 23 | impl_channel_take!(mpsc::Receiver); 24 | 25 | #[async_trait] 26 | impl crate::Sender for mpsc::Sender 27 | where 28 | T: Debug + Send, 29 | { 30 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 31 | mpsc::Sender::send(self, value) 32 | .await 33 | .map_err(|err| LifelineSendError::Return(err.0)) 34 | } 35 | } 36 | 37 | #[async_trait] 38 | impl crate::Receiver for mpsc::Receiver 39 | where 40 | T: Debug + Send, 41 | { 42 | async fn recv(&mut self) -> Option { 43 | mpsc::Receiver::recv(self).await 44 | } 45 | } 46 | 47 | impl Channel for broadcast::Sender { 48 | type Tx = Self; 49 | type Rx = broadcast::Receiver; 50 | 51 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 52 | broadcast::channel(capacity) 53 | } 54 | 55 | fn default_capacity() -> usize { 56 | 16 57 | } 58 | 59 | fn clone_rx(rx: &mut Option, tx: Option<&Self::Tx>) -> Option { 60 | // tokio channels have a size-limited queue 61 | // if one receiver stops processing messages, 62 | // the senders block 63 | 64 | // we take from rx first, getting the bus out of the way 65 | // then we subscribe using the sender 66 | // tx should always be here, but just in case.. tx.map( ... ) 67 | rx.take().or_else(|| tx.map(|tx| tx.subscribe())) 68 | } 69 | } 70 | 71 | impl_channel_clone!(broadcast::Sender); 72 | 73 | // this is actually overriden in clone_rx 74 | impl_channel_take!(broadcast::Receiver); 75 | 76 | #[async_trait] 77 | impl crate::Sender for broadcast::Sender 78 | where 79 | T: Debug + Send, 80 | { 81 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 82 | broadcast::Sender::send(self, value) 83 | .map(|_| ()) 84 | .map_err(|err| LifelineSendError::Return(err.0)) 85 | } 86 | } 87 | 88 | #[async_trait] 89 | impl crate::Receiver for broadcast::Receiver 90 | where 91 | T: Clone + Debug + Send, 92 | { 93 | async fn recv(&mut self) -> Option { 94 | loop { 95 | let result = broadcast::Receiver::recv(self).await; 96 | 97 | match result { 98 | Ok(t) => return Some(t), 99 | Err(broadcast::error::RecvError::Closed) => return None, 100 | Err(broadcast::error::RecvError::Lagged(n)) => { 101 | // we keep the broadcast complexity localized here. 102 | // instead of making things very complicated for mpsc, watch, etc receivers, 103 | // we log a debug message when a lag occurs, even if logging was not requested. 104 | 105 | debug!("LAGGED {} {}", n, type_name::()); 106 | continue; 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | impl Channel for oneshot::Sender { 114 | type Tx = Self; 115 | type Rx = oneshot::Receiver; 116 | 117 | fn channel(_capacity: usize) -> (Self::Tx, Self::Rx) { 118 | oneshot::channel() 119 | } 120 | 121 | fn default_capacity() -> usize { 122 | 1 123 | } 124 | } 125 | 126 | impl_channel_take!(oneshot::Sender); 127 | impl_channel_take!(oneshot::Receiver); 128 | 129 | impl Channel for watch::Sender 130 | where 131 | T: Default + Clone + Send + Sync + 'static, 132 | { 133 | type Tx = Self; 134 | type Rx = watch::Receiver; 135 | 136 | fn channel(_capacity: usize) -> (Self::Tx, Self::Rx) { 137 | watch::channel(T::default()) 138 | } 139 | 140 | fn default_capacity() -> usize { 141 | 1 142 | } 143 | } 144 | 145 | impl_channel_take!(watch::Sender); 146 | impl_channel_clone!(watch::Receiver); 147 | 148 | #[async_trait] 149 | impl crate::Sender for watch::Sender 150 | where 151 | T: Clone + Debug + Send + Sync, 152 | { 153 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 154 | watch::Sender::send(self, value).map_err(|_| LifelineSendError::Closed) 155 | } 156 | } 157 | 158 | #[async_trait] 159 | impl crate::Receiver for watch::Receiver 160 | where 161 | T: Clone + Debug + Send + Sync, 162 | { 163 | async fn recv(&mut self) -> Option { 164 | match self.changed().await { 165 | Ok(_) => Some(self.borrow().clone()), 166 | Err(_) => None, 167 | } 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /src/channel/postage.rs: -------------------------------------------------------------------------------- 1 | use super::Channel; 2 | use crate::{error::SendError as LifelineSendError, impl_storage_clone, impl_storage_take}; 3 | use crate::{impl_channel_clone, impl_channel_take}; 4 | use async_trait::async_trait; 5 | use postage::sink::Sink; 6 | use postage::stream::Stream; 7 | use postage::{barrier, broadcast, dispatch, mpsc, oneshot, watch}; 8 | use std::fmt::Debug; 9 | 10 | // barrier 11 | impl Channel for barrier::Sender { 12 | type Tx = Self; 13 | type Rx = barrier::Receiver; 14 | 15 | fn channel(_capacity: usize) -> (Self::Tx, Self::Rx) { 16 | barrier::channel() 17 | } 18 | 19 | fn default_capacity() -> usize { 20 | 1 21 | } 22 | } 23 | 24 | impl_storage_take!(barrier::Sender); 25 | impl_storage_clone!(barrier::Receiver); 26 | 27 | // broadcast 28 | impl Channel for broadcast::Sender { 29 | type Tx = Self; 30 | type Rx = broadcast::Receiver; 31 | 32 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 33 | broadcast::channel(capacity) 34 | } 35 | 36 | fn default_capacity() -> usize { 37 | 16 38 | } 39 | 40 | fn clone_rx(rx: &mut Option, tx: Option<&Self::Tx>) -> Option { 41 | rx.take().or_else(|| tx.map(|tx| tx.subscribe())) 42 | } 43 | } 44 | 45 | impl_channel_clone!(broadcast::Sender); 46 | impl_channel_take!(broadcast::Receiver); 47 | 48 | #[async_trait] 49 | impl crate::Sender for broadcast::Sender 50 | where 51 | T: Clone + Debug + Send, 52 | { 53 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 54 | Sink::send(self, value) 55 | .await 56 | .map(|_| ()) 57 | .map_err(|err| LifelineSendError::Return(err.0)) 58 | } 59 | } 60 | 61 | #[async_trait] 62 | impl crate::Receiver for broadcast::Receiver 63 | where 64 | T: Clone + Debug + Send, 65 | { 66 | async fn recv(&mut self) -> Option { 67 | Stream::recv(self).await 68 | } 69 | } 70 | 71 | // mpsc 72 | impl Channel for mpsc::Sender { 73 | type Tx = Self; 74 | type Rx = mpsc::Receiver; 75 | 76 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 77 | mpsc::channel(capacity) 78 | } 79 | 80 | fn default_capacity() -> usize { 81 | 16 82 | } 83 | } 84 | 85 | impl_channel_clone!(mpsc::Sender); 86 | impl_channel_take!(mpsc::Receiver); 87 | 88 | #[async_trait] 89 | impl crate::Sender for mpsc::Sender 90 | where 91 | T: Debug + Send, 92 | { 93 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 94 | Sink::send(self, value) 95 | .await 96 | .map_err(|err| LifelineSendError::Return(err.0)) 97 | } 98 | } 99 | 100 | #[async_trait] 101 | impl crate::Receiver for mpsc::Receiver 102 | where 103 | T: Debug + Send, 104 | { 105 | async fn recv(&mut self) -> Option { 106 | Stream::recv(self).await 107 | } 108 | } 109 | 110 | // dispatch 111 | impl Channel for dispatch::Sender { 112 | type Tx = Self; 113 | type Rx = dispatch::Receiver; 114 | 115 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 116 | dispatch::channel(capacity) 117 | } 118 | 119 | fn default_capacity() -> usize { 120 | 16 121 | } 122 | } 123 | 124 | impl_channel_clone!(dispatch::Sender); 125 | impl_channel_clone!(dispatch::Receiver); 126 | 127 | #[async_trait] 128 | impl crate::Sender for dispatch::Sender 129 | where 130 | T: Debug + Send, 131 | { 132 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 133 | Sink::send(self, value) 134 | .await 135 | .map_err(|err| LifelineSendError::Return(err.0)) 136 | } 137 | } 138 | 139 | #[async_trait] 140 | impl crate::Receiver for dispatch::Receiver 141 | where 142 | T: Debug + Send, 143 | { 144 | async fn recv(&mut self) -> Option { 145 | Stream::recv(self).await 146 | } 147 | } 148 | 149 | // oneshot 150 | impl Channel for oneshot::Sender { 151 | type Tx = Self; 152 | type Rx = oneshot::Receiver; 153 | 154 | fn channel(_capacity: usize) -> (Self::Tx, Self::Rx) { 155 | oneshot::channel() 156 | } 157 | 158 | fn default_capacity() -> usize { 159 | 1 160 | } 161 | } 162 | 163 | impl_channel_take!(oneshot::Sender); 164 | impl_channel_take!(oneshot::Receiver); 165 | 166 | impl Channel for watch::Sender 167 | where 168 | T: Default + Clone + Send + Sync + 'static, 169 | { 170 | type Tx = Self; 171 | type Rx = watch::Receiver; 172 | 173 | fn channel(_capacity: usize) -> (Self::Tx, Self::Rx) { 174 | watch::channel() 175 | } 176 | 177 | fn default_capacity() -> usize { 178 | 1 179 | } 180 | } 181 | 182 | impl_channel_take!(watch::Sender); 183 | impl_channel_clone!(watch::Receiver); 184 | 185 | #[async_trait] 186 | impl crate::Sender for watch::Sender 187 | where 188 | T: Clone + Debug + Send + Sync, 189 | { 190 | async fn send(&mut self, value: T) -> Result<(), LifelineSendError> { 191 | Sink::send(self, value) 192 | .await 193 | .map_err(|_| LifelineSendError::Closed) 194 | } 195 | } 196 | 197 | #[async_trait] 198 | impl crate::Receiver for watch::Receiver 199 | where 200 | T: Clone + Debug + Send + Sync, 201 | { 202 | async fn recv(&mut self) -> Option { 203 | Stream::recv(self).await 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! All the lifeline error types. 2 | 3 | use crate::Link; 4 | use regex::Regex; 5 | use std::fmt::Debug; 6 | use thiserror::Error; 7 | 8 | /// Utility function which turns an error into it's debug message as an anyhow::Error. 9 | pub fn into_msg(err: Err) -> anyhow::Error { 10 | let message = format!("{:?}", err); 11 | anyhow::Error::msg(message) 12 | } 13 | 14 | /// An error produced when calling `lifeline::Sender::send` 15 | #[derive(Error, Debug, PartialEq)] 16 | #[error("send error: ")] 17 | pub enum SendError { 18 | /// The channel has been closed, and the value is returned 19 | #[error("channel closed, message: {0:?}")] 20 | Return(T), 21 | 22 | /// The channel has been closed, but no value was returned 23 | #[error("channel closed")] 24 | Closed, 25 | } 26 | 27 | pub(crate) fn type_name() -> String { 28 | let name = std::any::type_name::(); 29 | 30 | let regex = Regex::new("[a-z][A-Za-z0-9_]+::").expect("Regex compiles"); 31 | regex.replace_all(name, "").to_string() 32 | } 33 | 34 | /// An error produced when attempting to take a Sender or Receiver from the bus. 35 | #[derive(Error, Debug)] 36 | pub enum TakeChannelError { 37 | /// The channel was partially linked on the bus, and this endpoint was not set. 38 | #[error("channel endpoints partially taken: {0}")] 39 | PartialTake(NotTakenError), 40 | 41 | /// The channel was already linked, and the requested operation required a new channel endpoint 42 | #[error("channel already linked: {0}")] 43 | AlreadyLinked(AlreadyLinkedError), 44 | 45 | /// The channel endpoint is not clonable, and the link was already taken 46 | #[error("channel already taken: {0}")] 47 | AlreadyTaken(LinkTakenError), 48 | } 49 | 50 | impl TakeChannelError { 51 | pub fn partial_take(link: Link) -> Self { 52 | Self::PartialTake(NotTakenError::new::(link)) 53 | } 54 | 55 | pub fn already_linked() -> Self { 56 | Self::AlreadyLinked(AlreadyLinkedError::new::()) 57 | } 58 | 59 | pub fn already_taken(link: Link) -> Self { 60 | Self::AlreadyTaken(LinkTakenError::new::(link)) 61 | } 62 | } 63 | 64 | /// The described endpoint could not be taken from the bus 65 | #[derive(Error, Debug)] 66 | #[error("endpoint not taken: {bus} < {message}::{link} >")] 67 | pub struct NotTakenError { 68 | pub bus: String, 69 | pub message: String, 70 | pub link: Link, 71 | } 72 | 73 | impl NotTakenError { 74 | pub fn new(link: Link) -> Self { 75 | NotTakenError { 76 | bus: type_name::().to_string(), 77 | message: type_name::().to_string(), 78 | link, 79 | } 80 | } 81 | } 82 | 83 | /// The described endpoint was already taken from the bus 84 | #[derive(Error, Debug)] 85 | #[error("link already taken: {bus} < {message}::{link} >")] 86 | pub struct LinkTakenError { 87 | pub bus: String, 88 | pub message: String, 89 | pub link: Link, 90 | } 91 | 92 | impl LinkTakenError { 93 | pub fn new(link: Link) -> Self { 94 | LinkTakenError { 95 | bus: type_name::().to_string(), 96 | message: type_name::().to_string(), 97 | link, 98 | } 99 | } 100 | } 101 | 102 | /// The channel was already linked on the bus, but the operation required the creation of a new endpoint pair. 103 | #[derive(Error, Debug)] 104 | #[error("link already generated: {bus} < {message} >")] 105 | pub struct AlreadyLinkedError { 106 | pub bus: String, 107 | pub message: String, 108 | } 109 | 110 | impl AlreadyLinkedError { 111 | pub fn new() -> Self { 112 | AlreadyLinkedError { 113 | bus: type_name::().to_string(), 114 | message: type_name::().to_string(), 115 | } 116 | } 117 | } 118 | 119 | /// The resource was not initialized, or was not clonable and was already taken 120 | #[derive(Error, Debug)] 121 | pub enum TakeResourceError { 122 | /// The resource was uninitialized 123 | #[error("{0}")] 124 | Uninitialized(ResourceUninitializedError), 125 | 126 | /// The resource was not clonable, and had already been taken 127 | #[error("{0}")] 128 | Taken(ResourceTakenError), 129 | } 130 | 131 | impl TakeResourceError { 132 | pub fn uninitialized() -> Self { 133 | Self::Uninitialized(ResourceUninitializedError::new::()) 134 | } 135 | 136 | pub fn taken() -> Self { 137 | Self::Taken(ResourceTakenError::new::()) 138 | } 139 | } 140 | 141 | /// The resource was already taken from the bus 142 | #[derive(Error, Debug)] 143 | #[error("resource already taken: {bus} < {resource} >")] 144 | pub struct ResourceTakenError { 145 | pub bus: String, 146 | pub resource: String, 147 | } 148 | 149 | impl ResourceTakenError { 150 | pub fn new() -> Self { 151 | ResourceTakenError { 152 | bus: type_name::().to_string(), 153 | resource: type_name::().to_string(), 154 | } 155 | } 156 | } 157 | 158 | /// The resource was uninitialized on the bus 159 | #[derive(Error, Debug)] 160 | #[error("resource uninitialized: {bus} < {resource} >")] 161 | pub struct ResourceUninitializedError { 162 | pub bus: String, 163 | pub resource: String, 164 | } 165 | 166 | impl ResourceUninitializedError { 167 | pub fn new() -> Self { 168 | ResourceUninitializedError { 169 | bus: type_name::().to_string(), 170 | resource: type_name::().to_string(), 171 | } 172 | } 173 | } 174 | 175 | /// The resource was already initialized on the bus, and the operation required an uninitialized resource 176 | #[derive(Error, Debug)] 177 | #[error("resource already initialized: {bus} < {resource} >")] 178 | pub struct ResourceInitializedError { 179 | pub bus: String, 180 | pub resource: String, 181 | } 182 | 183 | impl ResourceInitializedError { 184 | pub fn new() -> Self { 185 | ResourceInitializedError { 186 | bus: type_name::().to_string(), 187 | resource: type_name::().to_string(), 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /examples/shutdown.rs: -------------------------------------------------------------------------------- 1 | use bus::ExampleBus; 2 | use lifeline::{assert_completes, assert_times_out, prelude::*}; 3 | use lifeline::{Receiver, Sender}; 4 | use message::{DomainShutdown, MainRecv, MainShutdown}; 5 | use service::MainService; 6 | use simple_logger::SimpleLogger; 7 | 8 | /// This example shows how to propagate shutdown events, and synchronize shutdown with local tasks. 9 | /// For documentation on basic concepts (bus/service/channels), see the 'hello' example. 10 | #[tokio::main] 11 | pub async fn main() -> anyhow::Result<()> { 12 | SimpleLogger::new().init().expect("log init failed"); 13 | 14 | let bus = ExampleBus::default(); 15 | 16 | let _service = MainService::spawn(&bus)?; 17 | 18 | let mut tx = bus.tx::()?; 19 | let mut tx_domain_shutdown = bus.tx::()?; 20 | let mut rx_main_shutdown = bus.rx::()?; 21 | drop(bus); 22 | 23 | // let's send a few messages for the service to process. 24 | tx.send(MainRecv::Hello).await?; 25 | 26 | // and let's trigger a domain shutdown. this will cause MainService to begin it's shutdown procedure 27 | tx_domain_shutdown.send(DomainShutdown {}).await?; 28 | 29 | // it shouldn't be ready yet though, because it waits for a goodbye message 30 | assert_times_out!(async { rx_main_shutdown.recv().await }); 31 | 32 | // send the goodbye, now the service should have transmitted the shutdown message 33 | tx.send(MainRecv::Goodbye).await?; 34 | assert_completes!(async { 35 | let msg = rx_main_shutdown.recv().await; 36 | assert_eq!(Some(MainShutdown {}), msg); 37 | }); 38 | 39 | println!("All done."); 40 | 41 | // in a real application, we would have here at the end: 42 | // rx_main_shutdown.recv().await 43 | 44 | // at the end of the scope, we drop the MainService value. 45 | // any tasks that the service (or the services it spawns) will immediately be cancelled 46 | // this makes it possible to locally reason about when spawned tasks will be terminated 47 | 48 | Ok(()) 49 | } 50 | 51 | mod message { 52 | #[derive(Debug, Clone)] 53 | pub enum MainRecv { 54 | Hello, 55 | Goodbye, 56 | } 57 | 58 | /// This is the main shutdown event. 59 | /// The main thread waits on this, and when received, it exits.n 60 | /// This causes all lifelines to be dropped and cancelled 61 | #[derive(Debug, Clone, PartialEq, Eq)] 62 | pub struct MainShutdown; 63 | 64 | /// This is a domain-specific shutdown event. 65 | /// This can be triggered by a service which focuses on one app area. 66 | /// Main is responsible for interpreting this event, and acting on it. 67 | /// Main may need to shut down other services, or use Barriers to synchronize shutdown. 68 | #[derive(Debug, Clone)] 69 | pub struct DomainShutdown; 70 | 71 | /// This is a barrier message. It's carried by a lifeline barrier channel 72 | /// Barrier channels behave a bit like oneshot channels. 73 | /// but they produce a value when they are dropped. 74 | #[derive(Debug, Default, Clone)] 75 | pub struct MainEventBarrier; 76 | } 77 | 78 | mod bus { 79 | use crate::message::{DomainShutdown, MainEventBarrier, MainRecv, MainShutdown}; 80 | use lifeline::prelude::*; 81 | use postage::barrier; 82 | use postage::mpsc; 83 | 84 | lifeline_bus!(pub struct ExampleBus); 85 | 86 | impl Message for MainRecv { 87 | type Channel = mpsc::Sender; 88 | } 89 | 90 | impl Message for DomainShutdown { 91 | type Channel = mpsc::Sender; 92 | } 93 | 94 | impl Message for MainShutdown { 95 | type Channel = mpsc::Sender; 96 | } 97 | 98 | impl Message for MainEventBarrier { 99 | type Channel = barrier::Sender; 100 | } 101 | } 102 | 103 | mod service { 104 | use super::bus::ExampleBus; 105 | use crate::message::{DomainShutdown, MainEventBarrier, MainRecv, MainShutdown}; 106 | use lifeline::prelude::*; 107 | use postage::{sink::Sink, stream::Stream}; 108 | 109 | pub struct MainService { 110 | _greet: Lifeline, 111 | _shutdown: Lifeline, 112 | } 113 | 114 | impl Service for MainService { 115 | type Bus = ExampleBus; 116 | type Lifeline = anyhow::Result; 117 | 118 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 119 | // Here we'll spawn a task which waits for a Goodbye message, then quits 120 | let _greet = { 121 | let mut rx = bus.rx::()?; 122 | let mut tx_barrier = bus.tx::()?; 123 | Self::try_task("greet", async move { 124 | while let Some(recv) = rx.recv().await { 125 | if let MainRecv::Goodbye = recv { 126 | break; 127 | } 128 | } 129 | 130 | // send an event barrier message 131 | // this would also occur automatically if the tx_barrier value was dropped 132 | tx_barrier.send(()).await?; 133 | 134 | Ok(()) 135 | }) 136 | }; 137 | 138 | // And we'll spawn a shutdown task which synchronizes shutdown events 139 | let _shutdown = { 140 | let mut rx_domain_shutdown = bus.rx::()?; 141 | let mut rx_barrier = bus.rx::()?; 142 | let mut tx_main_shutdown = bus.tx::()?; 143 | Self::try_task("shutdown", async move { 144 | // if we receive a domain shutdown, begin the shutdown process 145 | if let Some(_shutdown) = rx_domain_shutdown.recv().await { 146 | // wait for the barrier to complete 147 | rx_barrier.recv().await; 148 | // forward the shutdown message, ignoring any tx error 149 | // it's a good idea to do this in shutdown code, 150 | // as receivers may have already gotten the message and dropped 151 | tx_main_shutdown.send(MainShutdown {}).await.ok(); 152 | } 153 | 154 | Ok(()) 155 | }) 156 | }; 157 | 158 | Ok(Self { _greet, _shutdown }) 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /examples/impl_channel.rs: -------------------------------------------------------------------------------- 1 | // This example shows how to make a custom Sender/Receiver type compatible with the lifeline bus 2 | 3 | use bus::ChannelBus; 4 | use lifeline::Bus; 5 | use lifeline::{Receiver, Sender}; 6 | use message::ExampleMessage; 7 | 8 | #[tokio::main] 9 | pub async fn main() -> anyhow::Result<()> { 10 | let bus = ChannelBus::default(); 11 | let mut tx = bus.tx::()?; 12 | let rx = bus.rx::()?; 13 | 14 | let sent = tx.send(ExampleMessage {}).await; 15 | assert_eq!(Ok(()), sent); 16 | 17 | associated(rx).await; 18 | 19 | Ok(()) 20 | } 21 | 22 | /// The Sender & Receiver trait allows lifeline users to do this. it also works with &mut impl Receiver<...> 23 | /// It's really nice for switching between broadcast/mpsc channels on the bus. 24 | /// Their service implementatons can just work, even with major changes to how channels are actually bound. 25 | async fn associated(mut rx: impl Receiver) { 26 | let rx = rx.recv().await; 27 | assert_eq!(None, rx); 28 | } 29 | 30 | mod sender { 31 | use crate::receiver::ExampleReceiver; 32 | use async_trait::async_trait; 33 | use lifeline::error::SendError; 34 | use lifeline::{impl_channel_clone, Channel, Sender}; 35 | use std::{fmt::Debug, marker::PhantomData}; 36 | 37 | /// Define a dummy Sender type that implements clone 38 | 39 | pub struct ExampleSender { 40 | _t: PhantomData, 41 | } 42 | 43 | impl Clone for ExampleSender { 44 | fn clone(&self) -> Self { 45 | Self { _t: PhantomData } 46 | } 47 | } 48 | 49 | impl ExampleSender { 50 | pub fn new(_capacity: usize) -> Self { 51 | Self { _t: PhantomData } 52 | } 53 | 54 | /// Define a dummy send method that either 'sends' with Ok, or returns the value to the caller. 55 | /// This would be the real send implementation, that communicates with the Receiver. 56 | pub fn send(_value: T) -> Result<(), T> { 57 | Ok(()) 58 | } 59 | } 60 | 61 | /// Implement the 'Sender' trait for ExampleSender. 62 | /// If users 'use' the Sender trait, the Sender API will shadow our custom method. 63 | /// This provides a consistent API for users, who can switch between watch, mpsc, and broadcast channels. 64 | /// They also can write `impl Sender` for associated methods on Service impls. 65 | #[async_trait] 66 | impl Sender for ExampleSender 67 | where 68 | T: Debug + Send, 69 | { 70 | async fn send(&mut self, value: T) -> Result<(), SendError> { 71 | ExampleSender::send(value).map_err(|value| SendError::Return(value)) 72 | } 73 | } 74 | 75 | // Implement a 'clone' operation for `bus.rx::()` 76 | // This channel can be taken any number of times for the lifetime of the bus. 77 | // Note that this *doesn't* mean the channel endpoint can be moved to another bus. 78 | // That requires a Carrier. 79 | impl_channel_clone!(ExampleSender); 80 | 81 | /// This is the trait that the bus uses to construct the channel. 82 | /// It can be generic over T, but is implement for the channel Sender type. 83 | impl Channel for ExampleSender 84 | where 85 | T: Send + 'static, 86 | { 87 | // this controls the return type of the `bus.tx()` and `bus.rx()` call. 88 | type Tx = Self; 89 | type Rx = ExampleReceiver; 90 | 91 | // this is what the bus calls when it want's to 'link' a channel 92 | // (e.g. construct an endpoint pair, and storing in it's 'slot' for the channel) 93 | // channel endpoints can only be linked once in the lifetime of the bus. 94 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 95 | // construct and return the linked pair 96 | let tx = ExampleSender::new(capacity); 97 | let rx = ExampleReceiver::new(); 98 | 99 | (tx, rx) 100 | } 101 | 102 | // this controls the default channel capacity. 103 | // users can override this with `bus.capacity::(42)` 104 | fn default_capacity() -> usize { 105 | 100 106 | } 107 | } 108 | } 109 | 110 | mod receiver { 111 | use async_trait::async_trait; 112 | use lifeline::{impl_channel_take, Receiver}; 113 | use std::{fmt::Debug, marker::PhantomData}; 114 | 115 | /// Define a dummy Receiver type that implements clone 116 | #[derive(Clone)] 117 | pub struct ExampleReceiver { 118 | _t: PhantomData, 119 | } 120 | 121 | pub struct CustomError {} 122 | 123 | impl ExampleReceiver { 124 | pub fn new() -> Self { 125 | Self { _t: PhantomData } 126 | } 127 | 128 | /// Define a dummy recv method that returns an error 129 | /// Note that we will be forced to discard it in the Receiver implementation. 130 | /// More on that below. 131 | pub async fn recv() -> Result { 132 | Err(CustomError {}) 133 | } 134 | } 135 | 136 | /// Implement the 'Receiver' trait for ExampleReceiver. 137 | /// Note the return type of Option - this is because a disconnected sender is not an error for a service 138 | /// It is simply a termination condition. 139 | /// Some channel types (broadcast) have additional error conditions. 140 | /// Those should be logged at warn level, or ignored. 141 | #[async_trait] 142 | impl Receiver for ExampleReceiver 143 | where 144 | T: Debug + Send, 145 | { 146 | async fn recv(&mut self) -> Option { 147 | ExampleReceiver::recv().await.ok() 148 | } 149 | } 150 | 151 | // Implement a 'take' operation for `bus.rx::()` 152 | // Once taken, future calls will return an Err. 153 | impl_channel_take!(ExampleReceiver); 154 | } 155 | 156 | mod message { 157 | #[derive(Clone, Debug, PartialEq)] 158 | pub struct ExampleMessage {} 159 | } 160 | 161 | mod bus { 162 | use crate::{message::ExampleMessage, sender::ExampleSender}; 163 | use lifeline::prelude::*; 164 | 165 | lifeline_bus!(pub struct ChannelBus); 166 | 167 | // we link the message type, and the sender type here. 168 | // the bus requires the implementation of Channel and Storage. 169 | // the Sender/Receiver traits are optional, 170 | // but they significantly improve ergonomics for the user 171 | impl Message for ExampleMessage { 172 | type Channel = ExampleSender; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /examples/carrier.rs: -------------------------------------------------------------------------------- 1 | use bus::{MainBus, SubsurfaceBus}; 2 | use lifeline::prelude::*; 3 | use message::{main::MainSend, subsurface::SubsurfaceSend}; 4 | use service::HelloService; 5 | use simple_logger::SimpleLogger; 6 | 7 | /// This examples shows how to communicate between Bus instances using the CarryFrom trait 8 | /// When your application gets large, eventually you need to spawn new tasks as runtime (when a connection arrives) 9 | /// At that point, you should create a new Bus type, and a Bus instance for each connection. 10 | /// Your new bus probably needs to communicate with your main application bus, and you can use a Carrier to do this. 11 | #[tokio::main] 12 | pub async fn main() -> anyhow::Result<()> { 13 | SimpleLogger::new().init().expect("log init failed"); 14 | 15 | let main_bus = MainBus::default(); 16 | let subsurface_bus = SubsurfaceBus::default(); 17 | 18 | // the implementation is determined from the type of MainBus! 19 | // and it's type-safe - we can only call when it's implemented. 20 | let _carrier = subsurface_bus.carry_from(&main_bus)?; 21 | 22 | // if you try to spawn HelloService from main_bus, you'll get a compile error 23 | let _service = HelloService::spawn(&subsurface_bus)?; 24 | 25 | // let's pull MainSend off the main bus, to send some messages 26 | let tx = main_bus.tx::()?; 27 | 28 | // if you try to pull this from MainBus, you'll get a compile error 29 | let mut rx = subsurface_bus.rx::()?; 30 | 31 | // As soon as we are done pulling channels, we drop the busses. 32 | // The carrier will still run - it has grabbed all it's channels. 33 | drop(main_bus); 34 | drop(subsurface_bus); 35 | 36 | // let's send a few messages for the service to process. 37 | // in normal stack-based applications, these messages would compare to the arguments of the main function, 38 | 39 | tx.send(MainSend::Hello).await?; 40 | tx.send(MainSend::HelloSubsurface).await?; 41 | tx.send(MainSend::Goodbye).await?; 42 | 43 | let oh_hello = rx.recv().await; 44 | assert_eq!(Some(SubsurfaceSend::OhHello), oh_hello); 45 | println!("Subsurface says {:?}", oh_hello.unwrap()); 46 | 47 | println!("All done."); 48 | 49 | Ok(()) 50 | } 51 | 52 | /// These are the messages which our application uses to communicate. 53 | /// The messages are carried over channels, using an async library (tokio, async_std, futures). 54 | /// 55 | /// Send/Recv 56 | mod message { 57 | // If a message is only used by one bus, I like to keep it in it's own module 58 | pub mod main { 59 | #[derive(Debug, Clone)] 60 | pub enum MainSend { 61 | Hello, 62 | HelloSubsurface, 63 | Goodbye, 64 | } 65 | } 66 | 67 | pub mod subsurface { 68 | #[derive(Debug, Clone, Eq, PartialEq)] 69 | pub enum SubsurfaceSend { 70 | OhHello, 71 | } 72 | 73 | #[derive(Debug, Clone)] 74 | pub enum SubsurfaceRecv { 75 | Hello, 76 | } 77 | } 78 | } 79 | 80 | /// This is the lifeline bus. 81 | /// The bus carries channels (senders/receivers). 82 | /// The bus knows how to construct these channels, and is lazy, 83 | /// it constructs on demand. 84 | /// The bus also carries resources, which are useful for cloneable config structs, 85 | /// or structs required for initialization. 86 | mod bus { 87 | use crate::message::{ 88 | main::MainSend, 89 | subsurface::{SubsurfaceRecv, SubsurfaceSend}, 90 | }; 91 | use lifeline::prelude::*; 92 | use tokio::sync::mpsc; 93 | 94 | lifeline_bus!(pub struct MainBus); 95 | 96 | // This binds the message MainSend to the bus. 97 | // We have to specify the channel sender! 98 | // The the channel sender must implement the Channel trait 99 | impl Message for MainSend { 100 | type Channel = mpsc::Sender; 101 | } 102 | 103 | lifeline_bus!(pub struct SubsurfaceBus); 104 | 105 | impl Message for SubsurfaceRecv { 106 | type Channel = mpsc::Sender; 107 | } 108 | 109 | impl Message for SubsurfaceSend { 110 | type Channel = mpsc::Sender; 111 | } 112 | 113 | impl CarryFrom for SubsurfaceBus { 114 | // if you only need one task, you can return Lifeline 115 | // if you need many tasks, you can return a struct like services do. 116 | 117 | type Lifeline = anyhow::Result; 118 | fn carry_from(&self, from: &MainBus) -> Self::Lifeline { 119 | let mut rx_main = from.rx::()?; 120 | let tx_sub = self.tx::()?; 121 | 122 | let lifeline = Self::try_task("from_main", async move { 123 | while let Some(msg) = rx_main.recv().await { 124 | match msg { 125 | MainSend::HelloSubsurface => tx_sub.send(SubsurfaceRecv::Hello {}).await?, 126 | _ => {} 127 | } 128 | } 129 | 130 | Ok(()) 131 | }); 132 | 133 | Ok(lifeline) 134 | } 135 | } 136 | } 137 | 138 | /// This is the service. 139 | /// The service is a spawnable task that launches from the bus. 140 | /// Service spawn is **synchronous** - the spawn should not send/receive messages, and it should be branchless. 141 | /// This makes errors very predictable. If you take an MPSC receiver twice, you immediately get the error on startup. 142 | mod service { 143 | use super::bus::SubsurfaceBus; 144 | use crate::message::subsurface::SubsurfaceRecv; 145 | use crate::message::subsurface::SubsurfaceSend; 146 | use lifeline::prelude::*; 147 | 148 | pub struct HelloService { 149 | _greet: Lifeline, 150 | } 151 | 152 | impl Service for HelloService { 153 | type Bus = SubsurfaceBus; 154 | type Lifeline = anyhow::Result; 155 | 156 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 157 | // The generic args here are required, by design. 158 | // Type inference would be nice, but if you type the message name here, 159 | // you can GREP THE NAME! Just search an event name and you'll see: 160 | // - which bus(es) the event is carried on 161 | // - which services rx the event 162 | // - which services tx the event 163 | 164 | // also, rx before tx! somewhat like fn service(rx) -> tx {} 165 | let mut rx = bus.rx::()?; 166 | let tx = bus.tx::()?; 167 | 168 | let _greet = Self::try_task("greet", async move { 169 | while let Some(recv) = rx.recv().await { 170 | match recv { 171 | SubsurfaceRecv::Hello => { 172 | tx.send(SubsurfaceSend::OhHello).await?; 173 | } 174 | } 175 | } 176 | 177 | Ok(()) 178 | }); 179 | 180 | Ok(Self { _greet }) 181 | } 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /examples/hello.rs: -------------------------------------------------------------------------------- 1 | use bus::ExampleBus; 2 | use lifeline::prelude::*; 3 | use message::{ExampleRecv, ExampleSend}; 4 | use postage::{sink::Sink, stream::Stream}; 5 | use service::ExampleService; 6 | use simple_logger::SimpleLogger; 7 | 8 | /// Spawn a simple bus, and a service 9 | /// The service execution is tied to the 'lifeline' it returns 10 | /// If `service` is dropped, all it's tasks are cancelled. 11 | #[tokio::main] 12 | pub async fn main() -> anyhow::Result<()> { 13 | SimpleLogger::new().init().expect("log init failed"); 14 | 15 | // Bus construction is immediate, parameterless, and infallible. 16 | // All busses implement Default. 17 | let bus = ExampleBus::default(); 18 | 19 | // The value returned here is a *lifeline*. 20 | // The tasks spawned by the service to drive channels *immediately* stop when the service is dropped. 21 | // This means that when you construct a service, you control how long it runs for. 22 | // You can get a sense of what tasks a service runs by looking at the struct definition. 23 | let _service = ExampleService::spawn(&bus)?; 24 | 25 | // there is an important naming convention here 26 | // tx - for Sender channels 27 | // rx - for Recevier channels 28 | // ExampleSend - a message which is sent to the world (and the service sends) 29 | // ExampleRecv - a message which is sent to the service (and the service receives) 30 | 31 | // this side of the channel is 'contravariant'. 32 | // we rx a 'send' msg, and tx a 'recv' message. 33 | // if we were in the service, 34 | // we would rx a 'recv' message, and 'tx' a send message 35 | // this naming convention helps a lot when reading code 36 | 37 | // taking receivers out of the bus is fallible. behavior depends on the channel type 38 | // lifeline is designed to make failures predictable, and early (near bus construction). 39 | // it also keeps track of as much context as possible using anyhow. 40 | // when rx/tx are called, channels are either cloned (remain in the bus for other takers), or taken. 41 | // in general: 42 | // mpsc: clone Sender / take Receiver 43 | // broadcast: clone Sender / clone Receiver 44 | // oneshot: take Sender / take Receiver 45 | // watch: take Sender / clone Receiver 46 | 47 | // lifeline also tries to make the channel type easy to change. 48 | // it wraps the concrete sender/receiver types in an adapter type, 49 | // which implements the lifeline::Sender / lifeline::Receiver trait 50 | let mut rx = bus.rx::()?; 51 | let mut tx = bus.tx::()?; 52 | 53 | // The bus *stores* channel endpoints. 54 | // As soon as your bus has been used to spawn your service, 55 | // and take your channels, drop it! 56 | // Then your tasks will get correct 'disconnected' Nones/Errs. 57 | drop(bus); 58 | 59 | // let's send a few messages for the service to process. 60 | // in normal stack-based applications, these messages would compare to the arguments of the main function, 61 | tx.send(ExampleRecv::Hello).await?; 62 | tx.send(ExampleRecv::Goodbye).await?; 63 | 64 | let oh_hello = rx.recv().await; 65 | assert_eq!(Some(ExampleSend::OhHello), oh_hello); 66 | println!("Service says {:?}", oh_hello.unwrap()); 67 | 68 | let aww_ok = rx.recv().await; 69 | assert_eq!(Some(ExampleSend::AwwOk), aww_ok); 70 | println!("Service says {:?}", aww_ok.unwrap()); 71 | 72 | println!("All done."); 73 | 74 | Ok(()) 75 | } 76 | 77 | /// These are the messages which our application uses to communicate. 78 | /// The messages are carried over channels, using an async library (tokio, async_std, futures). 79 | /// 80 | /// Send/Recv 81 | mod message { 82 | #[derive(Debug, Clone, Eq, PartialEq)] 83 | pub enum ExampleSend { 84 | OhHello, 85 | AwwOk, 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | pub enum ExampleRecv { 90 | Hello, 91 | Goodbye, 92 | } 93 | } 94 | 95 | /// This is the lifeline bus. 96 | /// The bus carries channels (senders/receivers). 97 | /// The bus knows how to construct these channels, and is lazy, 98 | /// it constructs on demand. 99 | /// The bus also carries resources, which are useful for cloneable config structs, 100 | /// or structs required for initialization. 101 | mod bus { 102 | use crate::message::{ExampleRecv, ExampleSend}; 103 | use lifeline::prelude::*; 104 | use postage::mpsc; 105 | 106 | // This is a macro that generates an ExampleBus struct, 107 | // and implements DynBus for it. 108 | // DynBus stores the channels in Box slots, 109 | // and deals with all the dyn trait magic for us. 110 | lifeline_bus!(pub struct ExampleBus); 111 | 112 | // This binds the message ExampleRecv to the bus. 113 | // We have to specify the channel sender! 114 | // The the channel sender must implement the Channel trait 115 | impl Message for ExampleSend { 116 | type Channel = mpsc::Sender; 117 | } 118 | 119 | impl Message for ExampleRecv { 120 | type Channel = mpsc::Sender; 121 | } 122 | } 123 | 124 | /// This is the service. 125 | /// The service is a spawnable task that launches from the bus. 126 | /// Service spawn is **synchronous** - the spawn should not send/receive messages, and it should be branchless. 127 | /// This makes errors very predictable. If you take an MPSC receiver twice, you immediately get the error on startup. 128 | mod service { 129 | use super::bus::ExampleBus; 130 | use crate::message::{ExampleRecv, ExampleSend}; 131 | use lifeline::prelude::*; 132 | use postage::{sink::Sink, stream::Stream}; 133 | 134 | pub struct ExampleService { 135 | _greet: Lifeline, 136 | } 137 | 138 | impl Service for ExampleService { 139 | type Bus = ExampleBus; 140 | type Lifeline = anyhow::Result; 141 | 142 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 143 | // The generic args here are required, by design. 144 | // Type inference would be nice, but if you type the message name here, 145 | // you can GREP THE NAME! Just search an event name and you'll see: 146 | // - which bus(es) the event is carried on 147 | // - which services rx the event 148 | // - which services tx the event 149 | 150 | // also, rx before tx! somewhat like fn service(rx) -> tx {} 151 | let mut rx = bus.rx::()?; 152 | let mut tx = bus.tx::()?; 153 | 154 | let _greet = Self::try_task("greet", async move { 155 | while let Some(recv) = rx.recv().await { 156 | match recv { 157 | ExampleRecv::Hello => { 158 | tx.send(ExampleSend::OhHello).await?; 159 | } 160 | ExampleRecv::Goodbye => { 161 | tx.send(ExampleSend::AwwOk).await?; 162 | } 163 | } 164 | } 165 | 166 | Ok(()) 167 | }); 168 | 169 | Ok(Self { _greet }) 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/spawn.rs: -------------------------------------------------------------------------------- 1 | use futures_util::task::AtomicWaker; 2 | use std::fmt::Debug; 3 | use std::future::Future; 4 | use std::{ 5 | sync::{ 6 | atomic::{AtomicBool, Ordering}, 7 | Arc, 8 | }, 9 | task::Poll, 10 | }; 11 | 12 | use crate::error::type_name; 13 | use log::debug; 14 | use pin_project::pin_project; 15 | 16 | /// Executes the task, until the future completes, or the lifeline is dropped 17 | /// 18 | /// If the `tokio-executor` feature is enabled, then it is used to spawn the task 19 | /// 20 | /// Otherwise, if the `async-std-executor` feature is enabled, then it is used to spawn the task 21 | #[allow(unreachable_code)] 22 | pub(crate) fn spawn_task(name: String, fut: impl Future + Send + 'static) -> Lifeline 23 | where 24 | O: Debug + Send + 'static, 25 | { 26 | let inner = Arc::new(LifelineInner::new()); 27 | 28 | let service = LifelineFuture::new(name, fut, inner.clone()); 29 | 30 | #[cfg(feature = "tokio-executor")] 31 | { 32 | spawn_task_tokio(service); 33 | return Lifeline::new(inner); 34 | } 35 | 36 | #[cfg(feature = "async-std-executor")] 37 | { 38 | spawn_task_async_std(service); 39 | return Lifeline::new(inner); 40 | } 41 | } 42 | 43 | pub(crate) fn task_name(name: &str) -> String { 44 | type_name::().to_string() + "/" + name 45 | } 46 | 47 | /// Spawns a task using the tokio executor 48 | #[cfg(feature = "tokio-executor")] 49 | fn spawn_task_tokio(task: F) 50 | where 51 | F: Future + Send + 'static, 52 | O: Send + 'static, 53 | { 54 | tokio::spawn(task); 55 | } 56 | 57 | /// Spawns a task using the async-std executor 58 | #[cfg(feature = "async-std-executor")] 59 | fn spawn_task_async_std(task: F) 60 | where 61 | F: Future + Send + 'static, 62 | O: Send + 'static, 63 | { 64 | async_std::task::spawn(task); 65 | } 66 | 67 | /// A future which wraps another future, and immediately returns Poll::Ready if the associated lifeline handle has been dropped. 68 | /// 69 | /// This is the critical component of the lifeline library, which allows the transparent & immediate cancelleation of entire Service trees. 70 | #[pin_project] 71 | struct LifelineFuture { 72 | #[pin] 73 | future: F, 74 | name: String, 75 | inner: Arc, 76 | } 77 | 78 | impl LifelineFuture { 79 | pub fn new(name: String, future: F, inner: Arc) -> Self { 80 | debug!("START {}", &name); 81 | 82 | Self { 83 | name, 84 | future, 85 | inner, 86 | } 87 | } 88 | } 89 | 90 | impl Future for LifelineFuture 91 | where 92 | F::Output: Debug, 93 | { 94 | type Output = (); 95 | 96 | fn poll( 97 | mut self: std::pin::Pin<&mut Self>, 98 | cx: &mut std::task::Context<'_>, 99 | ) -> std::task::Poll { 100 | if self.inner.complete.load(Ordering::Relaxed) { 101 | debug!("CANCEL {}", self.name); 102 | return Poll::Ready(()); 103 | } 104 | 105 | // attempt to complete the future 106 | if let Poll::Ready(result) = self.as_mut().project().future.poll(cx) { 107 | debug!("END {} {:?}", self.name, result); 108 | self.inner.complete(); 109 | return Poll::Ready(()); 110 | } 111 | 112 | // Register to receive a wakeup if the future is aborted in the... future 113 | self.inner.task_waker.register(cx.waker()); 114 | 115 | // Check to see if the future was aborted between the first check and 116 | // registration. 117 | // Checking with `Relaxed` is sufficient because `register` introduces an 118 | // `AcqRel` barrier. 119 | if self.inner.complete.load(Ordering::Relaxed) { 120 | debug!("CANCEL {}", self.name); 121 | return Poll::Ready(()); 122 | } 123 | 124 | Poll::Pending 125 | } 126 | } 127 | 128 | /// A lifeline value, associated with a future spawned via the `Task` trait. When the lifeline is dropped, the associated future is immediately cancelled. 129 | /// 130 | /// Lifeline values can be combined into structs, and represent trees of cancellable tasks. 131 | /// 132 | /// Example: 133 | /// ``` 134 | /// use lifeline::Task; 135 | /// use lifeline::Lifeline; 136 | /// 137 | /// struct ExampleService {} 138 | /// impl ExampleService { 139 | /// fn my_method() -> Lifeline { 140 | /// Self::task("my_method", async move { 141 | /// // some impl 142 | /// }) 143 | /// } 144 | /// } 145 | /// ``` 146 | #[derive(Debug)] 147 | #[must_use = "if unused the service will immediately be cancelled"] 148 | pub struct Lifeline { 149 | inner: Arc, 150 | } 151 | 152 | impl Lifeline { 153 | pub(crate) fn new(inner: Arc) -> Self { 154 | Self { inner } 155 | } 156 | } 157 | 158 | impl Future for Lifeline { 159 | type Output = (); 160 | 161 | fn poll(self: std::pin::Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { 162 | if self.inner.complete.load(Ordering::Relaxed) { 163 | return Poll::Ready(()); 164 | } 165 | 166 | // Register to receive a wakeup if the future is aborted in the... future 167 | self.inner.lifeline_waker.register(cx.waker()); 168 | 169 | // Check to see if the future was aborted between the first check and 170 | // registration. 171 | // Checking with `Relaxed` is sufficient because `register` introduces an 172 | // `AcqRel` barrier. 173 | if self.inner.complete.load(Ordering::Relaxed) { 174 | return Poll::Ready(()); 175 | } 176 | 177 | Poll::Pending 178 | } 179 | } 180 | 181 | impl Drop for Lifeline { 182 | fn drop(&mut self) { 183 | self.inner.abort(); 184 | } 185 | } 186 | 187 | #[derive(Debug)] 188 | pub(crate) struct LifelineInner { 189 | task_waker: AtomicWaker, 190 | lifeline_waker: AtomicWaker, 191 | complete: AtomicBool, 192 | } 193 | 194 | impl LifelineInner { 195 | pub fn new() -> Self { 196 | LifelineInner { 197 | task_waker: AtomicWaker::new(), 198 | lifeline_waker: AtomicWaker::new(), 199 | complete: AtomicBool::new(false), 200 | } 201 | } 202 | 203 | pub fn abort(&self) { 204 | self.complete.store(true, Ordering::Relaxed); 205 | self.task_waker.wake(); 206 | } 207 | 208 | pub fn complete(&self) { 209 | self.complete.store(true, Ordering::Relaxed); 210 | self.lifeline_waker.wake(); 211 | } 212 | } 213 | 214 | #[cfg(test)] 215 | mod tests { 216 | 217 | use std::{future::Future, task::Poll}; 218 | 219 | use super::spawn_task; 220 | use crate::{assert_completes, assert_times_out}; 221 | 222 | struct Pending {} 223 | 224 | impl Future for Pending { 225 | type Output = (); 226 | 227 | fn poll( 228 | self: std::pin::Pin<&mut Self>, 229 | _cx: &mut std::task::Context<'_>, 230 | ) -> std::task::Poll { 231 | Poll::Pending 232 | } 233 | } 234 | 235 | #[tokio::test] 236 | async fn lifeline_running_await_times_out() { 237 | let lifeline = spawn_task("test_complete".to_string(), Pending {}); 238 | 239 | assert_times_out!(async move { 240 | lifeline.await; 241 | }); 242 | } 243 | 244 | #[tokio::test] 245 | async fn lifeline_running_completes() { 246 | let lifeline = spawn_task("test_complete".to_string(), async move {}); 247 | 248 | assert_completes!(async move { 249 | lifeline.await; 250 | }); 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/bus.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{AlreadyLinkedError, TakeChannelError, TakeResourceError}, 3 | Channel, Storage, 4 | }; 5 | 6 | use std::fmt::{Debug, Display}; 7 | 8 | /// Attaches a channel to the [Bus](./trait.Bus.html), carrying `Self` as a message. 9 | /// 10 | /// The Channel associated type should be the Sender of the channel which will carry this message. 11 | /// 12 | /// Once implemented, the [bus.rx::\()](trait.Bus.html#tymethod.rx), [bus.tx::\()](trait.Bus.html#tymethod.tx), and [bus.capacity::\()](trait.Bus.html#tymethod.capacity) methods can be called. 13 | /// 14 | /// ## Example: 15 | /// ``` 16 | /// use lifeline::prelude::*; 17 | /// use tokio::sync::mpsc; 18 | /// 19 | /// lifeline_bus!(pub struct ExampleBus); 20 | /// 21 | /// #[derive(Debug)] 22 | /// pub struct ExampleMessage; 23 | /// 24 | /// impl Message for ExampleMessage { 25 | /// type Channel = mpsc::Sender; 26 | /// } 27 | /// 28 | /// fn main() -> anyhow::Result<()> { 29 | /// let bus = ExampleBus::default(); 30 | /// let mut tx = bus.tx::()?; 31 | /// Ok(()) 32 | /// } 33 | /// ``` 34 | pub trait Message: Debug { 35 | type Channel: Channel; 36 | } 37 | 38 | /// Attaches a resource to the [Bus](./trait.Bus.html). This resource can accessed from the bus using [bus.resource::\()](trait.Bus.html#tymethod.resource). 39 | /// 40 | /// The resource must implement [Storage](./trait.Storage.html), which describes whether the resource is taken or cloned. 41 | /// 42 | /// Lifeline provides helper macros: [impl_storage_take!(MyResource)](./macro.impl_storage_take.html) and [impl_storage_clone!(MyResource)](./macro.impl_storage_clone.html). 43 | /// 44 | /// ## Example: 45 | /// ``` 46 | /// use lifeline::prelude::*; 47 | /// use lifeline::impl_storage_clone; 48 | /// use tokio::sync::mpsc; 49 | /// 50 | /// lifeline_bus!(pub struct ExampleBus); 51 | /// 52 | /// #[derive(Clone, Debug)] 53 | /// pub struct MyResource; 54 | /// impl_storage_clone!(MyResource); 55 | /// 56 | /// impl Resource for MyResource {} 57 | /// ``` 58 | pub trait Resource: Storage + Debug + Send {} 59 | 60 | /// Stores and distributes channel endpoints ([Senders](./trait.Sender.html) and [Receivers](./trait.Receiver.html)), as well as [Resource](./trait.Resource.html) values. 61 | /// 62 | /// The bus allows you to write loosely-coupled applications, with adjacent lifeline [Services](./trait.Service.html) that do not depend on each other. 63 | /// 64 | /// Most Bus implementations are defined using the [lifeline_bus!](./macro.lifeline_bus.html) macro. 65 | /// 66 | /// ## Example: 67 | /// ``` 68 | /// use lifeline::lifeline_bus; 69 | /// 70 | /// lifeline_bus!(pub struct ExampleBus); 71 | /// ``` 72 | pub trait Bus: Default + Debug + Sized { 73 | /// Configures the channel capacity, if the linked channel implementation takes a capacity during initialization 74 | /// 75 | /// Returns an [AlreadyLinkedError](./error/struct.AlreadyLinkedError.html), if the channel has already been initalized from another call to `capacity`, `rx`, or `tx`. 76 | /// 77 | /// ## Example: 78 | /// ``` 79 | /// use lifeline::prelude::*; 80 | /// use tokio::sync::mpsc; 81 | /// lifeline_bus!(pub struct ExampleBus); 82 | /// 83 | /// #[derive(Debug)] 84 | /// struct ExampleMessage {} 85 | /// impl Message for ExampleMessage { 86 | /// type Channel = mpsc::Sender; 87 | /// } 88 | /// 89 | /// fn main() { 90 | /// let bus = ExampleBus::default(); 91 | /// bus.capacity::(1024); 92 | /// let rx = bus.rx::(); 93 | /// } 94 | fn capacity(&self, capacity: usize) -> Result<(), AlreadyLinkedError> 95 | where 96 | Msg: Message + 'static; 97 | 98 | /// Takes (or clones) the channel [Receiver](./trait.Receiver.html). The message type must implement [Message\](./trait.Message.html), which defines the channel type. 99 | /// 100 | /// Returns the [Receiver](./trait.Receiver.html), or a [TakeChannelError](./error/enum.TakeChannelError.html) if the channel endpoint is not clonable, and has already been taken. 101 | /// 102 | /// - For `mpsc` channels, the Receiver is taken. 103 | /// - For `broadcast` channels, the Receiver is cloned. 104 | /// - For `watch` channels, the Receiver is cloned. 105 | /// 106 | /// ## Example: 107 | /// ``` 108 | /// use lifeline::prelude::*; 109 | /// use tokio::sync::mpsc; 110 | /// lifeline_bus!(pub struct ExampleBus); 111 | /// 112 | /// #[derive(Debug)] 113 | /// struct ExampleMessage {} 114 | /// impl Message for ExampleMessage { 115 | /// type Channel = mpsc::Sender; 116 | /// } 117 | /// 118 | /// fn main() { 119 | /// let bus = ExampleBus::default(); 120 | /// let rx = bus.rx::(); 121 | /// } 122 | /// ``` 123 | fn rx(&self) -> Result<::Rx, TakeChannelError> 124 | where 125 | Msg: Message + 'static; 126 | 127 | /// Takes (or clones) the channel [Sender](./trait.Sender.html). The message type must implement [Message\](./trait.Message.html), which defines the channel type. 128 | /// 129 | /// Returns the sender, or a [TakeChannelError](./error/enum.TakeChannelError.html) if the channel endpoint is not clonable, and has already been taken. 130 | /// 131 | /// - For `mpsc` channels, the Sender is cloned. 132 | /// - For `broadcast` channels, the Sender is cloned. 133 | /// - For `watch` channels, the Sender is taken. 134 | /// 135 | /// ## Example: 136 | /// ``` 137 | /// use lifeline::prelude::*; 138 | /// use tokio::sync::mpsc; 139 | /// lifeline_bus!(pub struct ExampleBus); 140 | /// 141 | /// #[derive(Debug)] 142 | /// struct ExampleMessage {} 143 | /// impl Message for ExampleMessage { 144 | /// type Channel = mpsc::Sender; 145 | /// } 146 | /// 147 | /// fn main() { 148 | /// let bus = ExampleBus::default(); 149 | /// let tx = bus.tx::(); 150 | /// } 151 | /// ``` 152 | fn tx(&self) -> Result<::Tx, TakeChannelError> 153 | where 154 | Msg: Message + 'static; 155 | 156 | /// Takes (or clones) the [Resource](./trait.Resource.html). 157 | /// 158 | /// Returns the resource, or a [TakeResourceError](./error/enum.TakeResourceError.html) if the resource is not clonable, and has already been taken. 159 | /// 160 | /// ## Example: 161 | /// ``` 162 | /// use lifeline::prelude::*; 163 | /// use lifeline::impl_storage_clone; 164 | /// use tokio::sync::mpsc; 165 | /// lifeline_bus!(pub struct ExampleBus); 166 | /// 167 | /// #[derive(Debug, Clone)] 168 | /// struct ExampleResource {} 169 | /// impl_storage_clone!(ExampleResource); 170 | /// impl Resource for ExampleResource {} 171 | /// 172 | /// fn main() { 173 | /// let bus = ExampleBus::default(); 174 | /// let resource = bus.resource::(); 175 | /// } 176 | /// ``` 177 | fn resource(&self) -> Result 178 | where 179 | Res: Resource; 180 | } 181 | 182 | /// Represents the Sender, Receiver, or Both. Used in error types. 183 | #[derive(Debug)] 184 | pub enum Link { 185 | /// The Sender half of the channel 186 | Tx, 187 | /// The Receiver half of the channel 188 | Rx, 189 | /// Both the Sender and Receiver endpoints 190 | Both, 191 | } 192 | 193 | impl Display for Link { 194 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 195 | match self { 196 | Link::Tx => f.write_str("Tx"), 197 | Link::Rx => f.write_str("Rx"), 198 | Link::Both => f.write_str("Both"), 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Lifeline is a dependency injection library for message-based applications. Lifeline produces applications which are: 2 | //! - **Clean:** Bus implementations provide a high-level overview of the application, and services clearly define the messages they send and receive. 3 | //! - **Decoupled:** Services and tasks have no dependency on their peers, as they only depend on the message types they are sending or receiving. 4 | //! - **Stoppable:** Services and tasks are trivially cancellable. For example, you can terminate all tasks associated with a connection when a client disconnects. 5 | //! - **Greppable:** The impact/reach of a message can be easily understood by searching for the type in the source code. 6 | //! - **Testable:** Lifeline applications communicate via messages, which makes unit testing easy. Spawn the service, send a message, and expect a received message. 7 | //! 8 | //! In order to achieve these goals, lifeline provides patterns, traits, and implementations: 9 | //! - The [Bus](./trait.Bus.html), which constructs & distributes channel Senders/Receivers, and Resources. 10 | //! - The [Carrier](./trait.CarryFrom.html), which translates messages between two Bus instances. Carriers are critical when building large applications, and help minimize the complexity of the messages on each bus. 11 | //! - The [Service](./trait.Service.html), which takes channels from the bus, and spawns tasks which send and receive messages. 12 | //! - The [Task](./trait.Task.html), an async future which returns a lifeline when spawned. When the lifeline is dropped, the future is immedately cancelled. 13 | //! - The [Resource](./trait.Resource.html), a struct which can be stored in the bus, and taken (or cloned) when services spawn. 14 | //! 15 | //! For a quick introduction, see the [hello.rs example.](https://github.com/austinjones/lifeline-rs/blob/master/examples/hello.rs) 16 | //! For a full-scale application see [tab-rs.](https://github.com/austinjones/tab-rs) 17 | //! 18 | //! ## Quickstart 19 | //! Lifeline can be used with the [tokio](https://docs.rs/tokio/) and [async-std](https://docs.rs/async-std/) runtimes. By default, lifeline uses `tokio`. 20 | //! ```toml 21 | //! lifeline = "0.6" 22 | //! ``` 23 | //! 24 | //! [async-std](https://docs.rs/async-std/) can be enabled with the `async-std-executor` feature. And the `mpsc` implementation can be enabled with the `async-std-channels` feature: 25 | //! ```toml 26 | //! lifeline = { version = "0.6", default-features = false, features = ["dyn-bus", "async-std-executor", "async-std-channels"] } 27 | //! ``` 28 | //! 29 | //! Lifeline also supports [postage channels](https://docs.rs/postage/), a library that provides a portable set of channel implementations (compatible with any executor). 30 | //! Postage also provides Stream and Sink combinators (similar to futures StreamExt), that are optimized for async channels. 31 | //! Postage is intended to replace the LifelineSender/LifelineReceiver wrappers that were removed in lifeline v0.6.0. 32 | //! 33 | //! ## Upgrading 34 | //! v0.6.0 contains several breaking changes: 35 | //! - The LifelineSender and LifelineReceiver wrappers were removed. This was necessary due to the recent changes in the Stream ecosystem, and the upcoming stabilization of the Stream RFC. 36 | //! If you need Stream/Sink combinators, take a look at [postage](https://crates.io/crates/postage), or [tokio-stream](https://crates.io/crates/tokio-stream). 37 | //! - The barrier channel was removed. It can be replaced with [postage::barrier](https://docs.rs/postage/0.3.1/postage/barrier/index.html). 38 | //! - The subscription channel was removed. If you need it back, you can find the code before the removal [here](https://github.com/austinjones/lifeline-rs/blob/b15ab2342abcfa9c553d403cb58d2403531bf89c/src/channel/subscription.rs). 39 | //! - The Sender and Receiver traits were removed from prelude. This is so that importing the lifeline prelude does not conflict with Sink/Stream traits. You can import them with: 40 | //! `use lifeline::{Sender, Receiver}`. 41 | //! 42 | //! ## The Bus 43 | //! The [Bus](./trait.Bus.html) carries channels and resources, and allows you to write loosely coupled [Service](./trait.Service.html) implementations which communicate over messages. 44 | //! 45 | //! Channels can be taken from the bus. If the channel endpoint is clonable, it will remain available for other services. 46 | //! If the channel is not clonable, future calls will receive an `Err` value. The Rx/Tx type parameters are type-safe, 47 | //! and will produce a compile error if you attempt to take a channel for an message type which the bus does not carry. 48 | //! 49 | //! Lifeline provides a [lifeline_bus!](macro.lifeline_bus.html) macro which stores channels and resources in `Box` slots: 50 | //! ``` 51 | //! use lifeline::lifeline_bus; 52 | //! lifeline_bus!(pub struct MainBus); 53 | //! ``` 54 | //! 55 | //! ## The Carrier 56 | //! [Carriers](./trait.CarryFrom.html) provide a way to move messages between busses. [Carriers](./trait.CarryFrom.html) can translate, ignore, or collect information, 57 | //! providing each bus with the messages that it needs. 58 | //! 59 | //! Large applications have a tree of Busses. This is good, it breaks your app into small chunks. 60 | //! ```text 61 | //! - MainBus 62 | //! | ConnectionListenerBus 63 | //! | | ConnectionBus 64 | //! | DomainSpecificBus 65 | //! | | ... 66 | //! ``` 67 | //! [Carriers](./trait.CarryFrom.html) allow each bus to define messages that minimally represent the information it's services need to function, and prevent an explosion of messages which are copied to all busses. 68 | //! 69 | //! [Carriers](./trait.CarryFrom.html) centralize the communication between busses, making large applications easier to reason about. 70 | //! 71 | //! ## The Service 72 | //! The [Service](./trait.Service.html) synchronously takes channels from the [Bus](./trait.Bus.html), and spawns a tree of async tasks (which send & receive messages). 73 | //! When spawned, the service returns one or more [Lifeline](./struct.Lifeline.html) values. When a [Lifeline](./struct.Lifeline.html) is dropped, the associated task is immediately cancelled. 74 | //! 75 | //! It's common for [Service::spawn](./trait.Service.html#tymethod.spawn) to return a Result. Taking channel endpoints is a fallible operation. Depending on the channel type, the endpoint may not be clonable. 76 | //! Lifeline clones endpoints when it can (e.g. for `mpsc::Sender`, `broadcast::*`, and `watch::Receiver`). Other endpoints are taken, removed, and future calls will return an Err. 77 | //! 78 | //! [Service::spawn](./trait.Service.html#tymethod.spawn) takes channels from the bus synchronously, which makes errors occur predictably and early. If you get an Err on an `mpsc::Receiver`, 79 | //! change it's binding in the bus to `broadcast::Sender`. 80 | //! 81 | //! ## The Task 82 | //! The [Task](./trait.Task.html) executes an Future, and returns a [Lifeline](./struct.Lifeline.html) when spawned. When the lifeline is dropped, the future is immediately cancelled. 83 | //! 84 | //! [Task](./trait.Task.html) trait is implemented for all types - you can import it and use `Self::task` in any type. In lifeline, it's 85 | //! most commonly used in Service implementations. 86 | //! 87 | //! ## The Resource 88 | //! [Resources](./trait.Resource.html) can be stored on the bus. This is very useful for configuration (e.g MainConfig), or connections (e.g. a TcpStream). 89 | //! 90 | //! [Resources](./trait.Resource.html) implement the [Storage](./trait.Storage.html) trait, which is easy with the [impl_storage_clone!](./macro.impl_storage_clone.html) and [impl_storage_take!](./macro.impl_storage_take.html) macros. 91 | 92 | mod bus; 93 | mod channel; 94 | 95 | #[cfg(feature = "dyn-bus")] 96 | pub mod dyn_bus; 97 | 98 | pub mod error; 99 | pub mod prelude; 100 | 101 | #[cfg(feature = "tokio-channels")] 102 | pub mod request; 103 | 104 | mod service; 105 | mod spawn; 106 | mod storage; 107 | 108 | // TODO: try to get this as cfg(test) 109 | pub mod test; 110 | 111 | pub use bus::*; 112 | pub use channel::lifeline::{Receiver, Sender}; 113 | 114 | pub use channel::Channel; 115 | pub use service::*; 116 | pub use storage::Storage; 117 | pub use storage::*; 118 | 119 | pub use spawn::Lifeline; 120 | -------------------------------------------------------------------------------- /src/dyn_bus/storage.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | bus::{Link, Message, Resource}, 3 | error::{type_name, AlreadyLinkedError, TakeChannelError, TakeResourceError}, 4 | Bus, Channel, 5 | }; 6 | 7 | use super::slot::BusSlot; 8 | use log::debug; 9 | use std::{ 10 | any::TypeId, 11 | collections::{HashMap, HashSet}, 12 | fmt::Debug, 13 | marker::PhantomData, 14 | sync::{RwLock, RwLockWriteGuard}, 15 | }; 16 | /// Dynamic bus storage based on trait object slots, for Senders, Receivers, and Resources. 17 | /// 18 | /// Most values are stored as `HashMap` 19 | #[derive(Debug)] 20 | pub struct DynBusStorage { 21 | state: RwLock, 22 | _bus: PhantomData, 23 | } 24 | 25 | /// The internal state for: 26 | /// - channels, a set of message TypeIds for which the channel has been linked 27 | /// - capacity, the map from messageTypeId to the overriden channel capacity 28 | /// - tx, the map from message TypeId to the channel sender 29 | /// - rx, the map from message TypeId to the channel receiver 30 | /// - resource, the map from resource TypeId to the resource value 31 | #[derive(Debug)] 32 | struct DynBusState { 33 | pub(crate) channels: HashSet, 34 | pub(crate) capacity: HashMap, 35 | pub(crate) tx: HashMap, 36 | pub(crate) rx: HashMap, 37 | pub(crate) resources: HashMap, 38 | } 39 | 40 | impl Default for DynBusState { 41 | fn default() -> Self { 42 | DynBusState { 43 | channels: HashSet::new(), 44 | capacity: HashMap::new(), 45 | tx: HashMap::new(), 46 | rx: HashMap::new(), 47 | resources: HashMap::new(), 48 | } 49 | } 50 | } 51 | 52 | impl Default for DynBusStorage { 53 | fn default() -> Self { 54 | DynBusStorage { 55 | state: RwLock::new(DynBusState::default()), 56 | _bus: PhantomData, 57 | } 58 | } 59 | } 60 | 61 | impl DynBusStorage { 62 | /// Links a channel on the bus, locking the state and inserting BusSlots for the sender/receiver pair 63 | pub fn link_channel(&self) 64 | where 65 | Msg: Message + 'static, 66 | { 67 | let id = TypeId::of::(); 68 | 69 | if let Some(mut state) = self.try_lock(id) { 70 | let capacity = state 71 | .capacity 72 | .get(&id) 73 | .copied() 74 | .unwrap_or(Msg::Channel::default_capacity()); 75 | 76 | let (tx, rx) = Msg::Channel::channel(capacity); 77 | 78 | debug!("{} linked in {}", type_name::(), type_name::()); 79 | state.rx.insert(id, BusSlot::new(Some(rx))); 80 | state.tx.insert(id, BusSlot::new(Some(tx))); 81 | 82 | state.channels.insert(id); 83 | } 84 | } 85 | 86 | /// Takes or clones the channel receiver, using the `Channel` trait implementation. 87 | /// Returns an error if the endpoint cannot be taken. 88 | pub fn clone_rx(&self) -> Result<::Rx, TakeChannelError> 89 | where 90 | Msg: Message + 'static, 91 | { 92 | self.link_channel::(); 93 | 94 | let id = TypeId::of::(); 95 | 96 | let mut state = self.state.write().unwrap(); 97 | let state = &mut *state; 98 | let tx = &state.tx; 99 | let rx = &mut state.rx; 100 | 101 | let tx = tx 102 | .get(&id) 103 | .map(|slot| slot.get_tx::()) 104 | .flatten(); 105 | 106 | let slot = rx 107 | .get_mut(&id) 108 | .ok_or_else(|| TakeChannelError::partial_take::(Link::Rx))?; 109 | 110 | slot.clone_rx::(tx) 111 | .ok_or_else(|| TakeChannelError::already_taken::(Link::Rx)) 112 | } 113 | 114 | /// Takes or clones the channel sender, using the `Channel` trait implementation. 115 | /// Returns an error if the endpoint cannot be taken. 116 | pub fn clone_tx(&self) -> Result<::Tx, TakeChannelError> 117 | where 118 | Msg: Message + 'static, 119 | { 120 | self.link_channel::(); 121 | 122 | let id = TypeId::of::(); 123 | 124 | let mut state = self.state.write().unwrap(); 125 | let senders = &mut state.tx; 126 | 127 | // if the channel is linked, but the slot is empty, 128 | // this means the user used take_rx, but asked for tx 129 | let slot = senders 130 | .get_mut(&id) 131 | .ok_or_else(|| TakeChannelError::partial_take::(Link::Tx))?; 132 | 133 | slot.clone_tx::() 134 | .ok_or_else(|| TakeChannelError::already_taken::(Link::Tx)) 135 | } 136 | 137 | /// Takes or clones the resource, using the `Storage` trait implementation. 138 | /// Returns an error if the resource cannot be taken. 139 | pub fn clone_resource(&self) -> Result 140 | where 141 | Res: Resource + 'static, 142 | { 143 | let id = TypeId::of::(); 144 | 145 | let mut state = self.state.write().unwrap(); 146 | let resources = &mut state.resources; 147 | let slot = resources 148 | .get_mut(&id) 149 | .ok_or_else(|| TakeResourceError::uninitialized::())?; 150 | 151 | slot.clone_storage::() 152 | .ok_or_else(|| TakeResourceError::taken::()) 153 | } 154 | 155 | /// Stores the resource on the bus, overwriting it if it already exists 156 | pub fn store_resource(&self, value: Res) { 157 | let id = TypeId::of::(); 158 | 159 | let mut state = self.state.write().unwrap(); 160 | let resources = &mut state.resources; 161 | 162 | if !resources.contains_key(&id) { 163 | resources.insert(id.clone(), BusSlot::empty::()); 164 | } 165 | 166 | debug!("{} stored in {}", type_name::(), type_name::()); 167 | 168 | let slot = resources.get_mut(&id).unwrap(); 169 | 170 | slot.put(value); 171 | } 172 | 173 | /// Stores the (Rx, Tx) pair, or either of them if Nones are provided. 174 | /// This consumes the bus slot for this message type. Future calls to store on this message type will fail. 175 | pub fn store_channel( 176 | &self, 177 | rx: Option, 178 | tx: Option, 179 | ) -> Result<(), AlreadyLinkedError> 180 | where 181 | Chan: Channel, 182 | Msg: 'static, 183 | { 184 | if rx.is_none() && tx.is_none() { 185 | return Ok(()); 186 | } 187 | 188 | let id = TypeId::of::(); 189 | 190 | let mut target = self.state.write().expect("cannot lock other"); 191 | if target.channels.contains(&id) { 192 | return Err(AlreadyLinkedError::new::()); 193 | } 194 | 195 | let link = match (rx.is_some(), tx.is_some()) { 196 | (true, true) => Link::Both, 197 | (true, false) => Link::Rx, 198 | (false, true) => Link::Tx, 199 | (false, false) => unreachable!(), 200 | }; 201 | 202 | debug!( 203 | "{}/{} stored in {}", 204 | type_name::(), 205 | link, 206 | type_name::(), 207 | ); 208 | 209 | target.channels.insert(id); 210 | target.tx.insert(id.clone(), BusSlot::new(tx)); 211 | target.rx.insert(id.clone(), BusSlot::new(rx)); 212 | 213 | Ok(()) 214 | } 215 | 216 | /// Writes a capacity to the bus storage, for the given message type. 217 | /// Returns an error if the channel is already linked in the bus storage (as this capacity would do nothing). 218 | pub fn capacity(&self, capacity: usize) -> Result<(), AlreadyLinkedError> 219 | where 220 | Msg: Message + 'static, 221 | { 222 | let id = TypeId::of::(); 223 | 224 | let state = self.state.read().unwrap(); 225 | 226 | if state.capacity.contains_key(&id) { 227 | return Err(AlreadyLinkedError::new::()); 228 | } 229 | 230 | drop(state); 231 | 232 | let mut state = self.state.write().unwrap(); 233 | 234 | state.capacity.insert(id, capacity); 235 | 236 | Ok(()) 237 | } 238 | 239 | /// Attempts to lock the bus, and acquire the state for the given message TypeId. 240 | fn try_lock(&self, id: TypeId) -> Option> { 241 | let state = self.state.read().unwrap(); 242 | if state.channels.contains(&id) { 243 | return None; 244 | } 245 | 246 | drop(state); 247 | 248 | let state = self.state.write().unwrap(); 249 | if state.channels.contains(&id) { 250 | return None; 251 | } 252 | 253 | Some(state) 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /src/service.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | spawn::{spawn_task, task_name}, 3 | Bus, Lifeline, 4 | }; 5 | use log::{debug, error}; 6 | use std::future::Future; 7 | use std::{any::TypeId, fmt::Debug}; 8 | 9 | /// Takes channels from the [Bus](./trait.Bus.html), and spawns a tree of tasks. Returns one or more [Lifeline](./struct.Lifeline.html) values. 10 | /// When the [Lifeline](./struct.Lifeline.html) is dropped, the task tree is immediately cancelled. 11 | /// 12 | /// - Simple implementations can return the [Lifeline](./struct.Lifeline.html) value, a handle returned by [Task::task](./trait.Task.html#method.task). 13 | /// - Implementations which have fallible spawns can return `anyhow::Result`. 14 | /// - Implementations which spawn multiple tasks can store lifelines for each task in self, and return `anyhow::Result`. 15 | /// 16 | /// ## Example 17 | /// ``` 18 | /// use lifeline::prelude::*; 19 | /// use tokio::sync::mpsc; 20 | /// 21 | /// lifeline_bus!(pub struct ExampleBus); 22 | /// 23 | /// #[derive(Debug, Clone)] 24 | /// struct ExampleMessage {} 25 | /// 26 | /// impl Message for ExampleMessage { 27 | /// type Channel = mpsc::Sender; 28 | /// } 29 | /// 30 | /// struct ExampleService { 31 | /// _run: Lifeline 32 | /// } 33 | /// 34 | /// impl Service for ExampleService { 35 | /// type Bus = ExampleBus; 36 | /// type Lifeline = anyhow::Result; 37 | /// 38 | /// fn spawn(bus: &ExampleBus) -> anyhow::Result { 39 | /// let mut rx = bus.rx::()?; 40 | /// 41 | /// let _run = Self::task("run", async move { 42 | /// while let Some(msg) = rx.recv().await { 43 | /// log::info!("got message: {:?}", msg); 44 | /// } 45 | /// }); 46 | /// 47 | /// Ok(Self { _run }) 48 | /// } 49 | /// } 50 | /// 51 | /// async fn run() { 52 | /// let bus = ExampleBus::default(); 53 | /// let _service = ExampleService::spawn(&bus); 54 | /// } 55 | /// ``` 56 | pub trait Service: Task { 57 | /// The bus, which must be provided to spawn the task 58 | type Bus: Bus; 59 | 60 | /// The service lifeline. When dropped, all spawned tasks are immediately cancelled. 61 | type Lifeline; 62 | 63 | /// Spawns the service with all sub-tasks, and returns a lifeline value. When the lifeline is dropped, all spawned tasks are immediately cancelled. 64 | /// 65 | /// Implementations should synchronously take channels from the bus, and then use them asynchronously. This makes errors occur as early and predictably as possible. 66 | fn spawn(bus: &Self::Bus) -> Self::Lifeline; 67 | } 68 | 69 | /// Constructs the bus, spawns the service, and returns both. 70 | pub trait DefaultService: Service { 71 | fn spawn_default() -> (Self::Bus, Self::Lifeline); 72 | } 73 | 74 | impl DefaultService for T 75 | where 76 | T: Service, 77 | { 78 | fn spawn_default() -> (Self::Bus, Self::Lifeline) { 79 | let bus = Self::Bus::default(); 80 | let lifeline = Self::spawn(&bus); 81 | 82 | (bus, lifeline) 83 | } 84 | } 85 | 86 | /// Carries messages between **two** bus instances. A variant of the [Service](./trait.Service.html). 87 | /// 88 | /// Bus types form a tree, with a 'root application' bus, and multiple busses focused on particular domains. This structure provides isolation, 89 | /// and predictable failures when [Services](./trait.Service.html) spawn. 90 | /// ```text 91 | /// - MainBus 92 | /// | ListenerBus 93 | /// | | ConnectionBus 94 | /// | DomainSpecificBus 95 | /// | | ... 96 | /// ``` 97 | /// 98 | /// This trait can be implemented to carry messages between the root and the leaf of the tree. 99 | /// 100 | /// ## Example 101 | /// ``` 102 | /// use lifeline::prelude::*; 103 | /// use tokio::sync::mpsc; 104 | /// lifeline_bus!(pub struct MainBus); 105 | /// lifeline_bus!(pub struct LeafBus); 106 | /// 107 | /// #[derive(Debug, Clone)] 108 | /// struct LeafShutdown {} 109 | /// 110 | /// #[derive(Debug, Clone)] 111 | /// struct MainShutdown {} 112 | /// 113 | /// impl Message for LeafShutdown { 114 | /// type Channel = mpsc::Sender; 115 | /// } 116 | /// 117 | /// impl Message for MainShutdown { 118 | /// type Channel = mpsc::Sender; 119 | /// } 120 | /// 121 | /// pub struct LeafMainCarrier { 122 | /// _forward_shutdown: Lifeline 123 | /// } 124 | /// 125 | /// impl CarryFrom for LeafBus { 126 | /// type Lifeline = anyhow::Result; 127 | /// fn carry_from(&self, from: &MainBus) -> Self::Lifeline { 128 | /// let mut rx = self.rx::()?; 129 | /// let mut tx = from.tx::()?; 130 | /// 131 | /// let _forward_shutdown = Self::try_task("forward_shutdown", async move { 132 | /// if let Some(msg) = rx.recv().await { 133 | /// tx.send(MainShutdown{}).await?; 134 | /// } 135 | /// 136 | /// Ok(()) 137 | /// }); 138 | /// 139 | /// Ok(LeafMainCarrier { _forward_shutdown }) 140 | /// } 141 | /// } 142 | /// ``` 143 | pub trait CarryFrom: Bus + Task + Sized { 144 | /// The carrier lifeline. When dropped, all spawned tasks are immediately cancelled. 145 | type Lifeline; 146 | 147 | /// Spawns the carrier service, returning the lifeline value. 148 | fn carry_from(&self, from: &FromBus) -> Self::Lifeline; 149 | } 150 | 151 | /// The receprocial of the [CarryFrom](./trait.CarryFrom.html) trait. Implemented for all types on which [CarryFrom](./trait.CarryFrom.html) is implemented. 152 | pub trait CarryInto: Bus + Task + Sized { 153 | /// The carrier lifeline. When dropped, all spawned tasks are immediately cancelled. 154 | type Lifeline; 155 | 156 | /// Spawns the carrier service, returning the lifeline value. 157 | fn carry_into(&self, into: &IntoBus) -> Self::Lifeline; 158 | } 159 | 160 | impl CarryInto for F 161 | where 162 | I: CarryFrom, 163 | F: Bus, 164 | I: Bus, 165 | { 166 | type Lifeline = >::Lifeline; 167 | 168 | fn carry_into(&self, into: &I) -> Self::Lifeline { 169 | into.carry_from(self) 170 | } 171 | } 172 | 173 | /// Constructs two bus types, and spawns the carrier between them. 174 | /// Returns both busses, and the carrier's lifeline. 175 | pub trait DefaultCarrier: CarryFrom { 176 | fn carry_default() -> (Self, FromBus, Self::Lifeline) { 177 | let into = Self::default(); 178 | let from = FromBus::default(); 179 | let lifeline = into.carry_from(&from); 180 | 181 | (into, from, lifeline) 182 | } 183 | } 184 | 185 | /// Provides the [Self::task](./trait.Task.html#method.task) and [Self::try_task](./trait.Task.html#method.try_task) associated methods for all types. 186 | /// 187 | /// Lifeline supports the following task executors (using feature flags), and will use the first enabled flag: 188 | /// - `tokio-executor` 189 | /// - `async-std-executor` 190 | /// 191 | /// Fallible tasks can be invoked with [Self::try_task](./trait.Task.html#method.try_task). Lifeline will log OK/ERR status when the task finishes. 192 | /// 193 | /// # Example 194 | /// ``` 195 | /// use lifeline::prelude::*; 196 | /// use tokio::sync::mpsc; 197 | /// 198 | /// lifeline_bus!(pub struct ExampleBus); 199 | /// 200 | /// #[derive(Debug, Clone)] 201 | /// struct ExampleMessage {} 202 | /// 203 | /// impl Message for ExampleMessage { 204 | /// type Channel = mpsc::Sender; 205 | /// } 206 | /// 207 | /// struct ExampleService { 208 | /// _run: Lifeline 209 | /// } 210 | /// 211 | /// impl Service for ExampleService { 212 | /// type Bus = ExampleBus; 213 | /// type Lifeline = anyhow::Result; 214 | /// 215 | /// fn spawn(bus: &ExampleBus) -> anyhow::Result { 216 | /// let mut rx = bus.rx::()?; 217 | /// 218 | /// let _run = Self::task("run", async move { 219 | /// while let Some(msg) = rx.recv().await { 220 | /// log::info!("got message: {:?}", msg); 221 | /// } 222 | /// }); 223 | /// 224 | /// Ok(Self { _run }) 225 | /// } 226 | /// } 227 | /// ``` 228 | pub trait Task { 229 | /// Spawns an infallible task using the provided executor, wrapping it in a [Lifeline](./struct.Lifeline.html) handle. 230 | /// The task will run until it finishes, or until the [Lifeline](./struct.Lifeline.html) is droped. 231 | fn task(name: &str, fut: impl Future + Send + 'static) -> Lifeline 232 | where 233 | Out: Debug + Send + 'static, 234 | Self: Sized, 235 | { 236 | let service_name = task_name::(name); 237 | spawn_task(service_name, fut) 238 | } 239 | 240 | /// Spawns an fallible task using the provided executor, wrapping it in a [Lifeline](./struct.Lifeline.html) handle. 241 | /// The task will run until it finishes, or until the [Lifeline](./struct.Lifeline.html) is droped. 242 | /// 243 | /// If the task finishes, lifeline will log an 'OK' or 'ERR' message with the return value. 244 | fn try_task( 245 | name: &str, 246 | fut: impl Future> + Send + 'static, 247 | ) -> Lifeline 248 | where 249 | Out: Debug + 'static, 250 | Self: Sized, 251 | { 252 | let service_name = task_name::(name); 253 | spawn_task(service_name.clone(), async move { 254 | match fut.await { 255 | Ok(val) => { 256 | if TypeId::of::() != TypeId::of::<()>() { 257 | debug!("OK {}: {:?}", service_name, val); 258 | } else { 259 | debug!("OK {}", service_name); 260 | } 261 | } 262 | Err(err) => { 263 | error!("ERR: {}: {}", service_name, err); 264 | } 265 | } 266 | }) 267 | } 268 | } 269 | 270 | impl Task for T {} 271 | -------------------------------------------------------------------------------- /examples/state.rs: -------------------------------------------------------------------------------- 1 | use bus::StateBus; 2 | use lifeline::prelude::*; 3 | use message::MainRecv; 4 | use postage::{sink::Sink, stream::Stream}; 5 | use service::{MainService, StateService}; 6 | use simple_logger::SimpleLogger; 7 | use state::{LocationState, SkyState, WeatherState}; 8 | use std::time::Duration; 9 | use tokio::time::sleep; 10 | 11 | /// This example shows how to maintain state in a service, and broadcast it using channels. 12 | /// For documentation on basic concepts (bus/service/channels), see the 'hello' example. 13 | #[tokio::main] 14 | pub async fn main() -> anyhow::Result<()> { 15 | SimpleLogger::new().init().expect("log init failed"); 16 | 17 | let bus = StateBus::default(); 18 | 19 | let _service = MainService::spawn(&bus)?; 20 | let _state = StateService::spawn(&bus)?; 21 | 22 | let mut tx = bus.tx::()?; 23 | let mut rx = bus.rx::()?; 24 | 25 | // The bus *stores* channel endpoints. 26 | // As soon as your bus has been used to spawn your service, 27 | // and take your channels, drop it! 28 | // Then your tasks will get correct 'disconnected' Nones/Errs. 29 | drop(bus); 30 | 31 | // let's send a few messages for the service to process. 32 | // in normal stack-based applications, these messages would compare to the arguments of the main function, 33 | tx.send(MainRecv::Travel(LocationState::Boston)).await?; 34 | 35 | // state updates are asynchronous. they may not be propagated immediately 36 | sleep(Duration::from_millis(20)).await; 37 | 38 | let state = rx.recv().await; 39 | let expected = SkyState { 40 | weather: WeatherState::Snowing, 41 | location: LocationState::Boston, 42 | }; 43 | 44 | // it's snowing in boston! 45 | assert_eq!(Some(expected), state); 46 | 47 | // 48 | // let's travel to san diego! 49 | // 50 | tx.send(MainRecv::Travel(LocationState::SanDiego)).await?; 51 | 52 | // state updates are asynchronous. they may not be propagated immediately 53 | sleep(Duration::from_millis(20)).await; 54 | 55 | let state = rx.recv().await; 56 | let expected = SkyState { 57 | weather: WeatherState::Sunny72Degrees, 58 | location: LocationState::SanDiego, 59 | }; 60 | 61 | // it's snowing in boston! 62 | assert_eq!(Some(expected), state); 63 | 64 | println!("All done."); 65 | 66 | Ok(()) 67 | } 68 | 69 | /// These are the messages which our application uses to communicate. 70 | /// The messages are carried over channels, using an async library (tokio, async_std, futures). 71 | /// 72 | /// Send/Recv 73 | mod message { 74 | // You might be tempted to write a struct here for MainRecv. 75 | // You can do that, but I like to write enums for service send/recvs. 76 | // It's much easier to add message types! 77 | 78 | use crate::state::LocationState; 79 | 80 | // If the message is not tied to the service recv (e.g. WeatherEvent), 81 | // then it's nice to write a struct. 82 | // Then multiple services can subscribe via a broadcast channel, and consume the event. 83 | #[derive(Debug, Clone)] 84 | pub enum MainRecv { 85 | Travel(LocationState), 86 | } 87 | 88 | // This is a one-off event. 89 | // It's carried on the bus, and isn't 'owned' by a service. 90 | // If the channel is mpsc, there can only be one receiver. 91 | // If the channel is broadcast, many services can send/receive the events. 92 | #[derive(Debug, Clone)] 93 | pub struct TravelEvent(pub LocationState); 94 | } 95 | 96 | // I like to keep state in a separate module. 97 | // State is very different from channels. 98 | // It is persistent, and it changes. 99 | // Messages are just transmitted and then immediately disposed. 100 | mod state { 101 | // This is a State struct. 102 | // It is mainained by a service, cloned, and commmunicated via channels. 103 | // Use pub fields if you need to communicate multiple values, or just a top-level enum. 104 | #[derive(Debug, Clone, Eq, PartialEq)] 105 | pub struct SkyState { 106 | pub weather: WeatherState, 107 | pub location: LocationState, 108 | } 109 | 110 | // Name your state structs with the State postfix! 111 | // Even though state is maintained in a service, it comes from 'the world'. 112 | // The service that maintains the state 'receives' it (though it may calculate it). 113 | // The service that uses the state 'recieves' it. 114 | // So the Send/Recv postfixes don't make sense. 115 | #[derive(Debug, Clone, Eq, PartialEq)] 116 | pub enum WeatherState { 117 | None, 118 | Snowing, 119 | Sunny72Degrees, 120 | } 121 | 122 | #[derive(Debug, Clone, Eq, PartialEq)] 123 | pub enum LocationState { 124 | None, 125 | Boston, 126 | SanDiego, 127 | } 128 | 129 | // States communicated over channels must implement Default! 130 | // This is because the Bus needs to initialize the channels without any arguments. 131 | impl Default for SkyState { 132 | fn default() -> Self { 133 | Self { 134 | weather: WeatherState::None, 135 | location: LocationState::None, 136 | } 137 | } 138 | } 139 | } 140 | 141 | /// This is the lifeline bus. 142 | /// The bus carries channels (senders/receivers). 143 | /// The bus knows how to construct these channels, and is lazy, 144 | /// it constructs on demand. 145 | /// The bus also carries resources, which are useful for cloneable config structs, 146 | /// or structs required for initialization. 147 | mod bus { 148 | use crate::message::{MainRecv, TravelEvent}; 149 | use crate::state::SkyState; 150 | use lifeline::prelude::*; 151 | use postage::{broadcast, mpsc, watch}; 152 | 153 | lifeline_bus!(pub struct StateBus); 154 | 155 | // we bind a watch sender here. 156 | // watch senders store the latest value, 157 | // and allow the receiver to either borrow, or clone. 158 | impl Message for SkyState { 159 | type Channel = watch::Sender; 160 | } 161 | 162 | // We bind a broadcast sender for events. 163 | // In this example, it isn't necessary, but it's useful in big apps. 164 | // If you want to downgrade a broadcast to mpsc, do it here, run your app, 165 | // and see if you get a TakeChannelError on service spawn. 166 | impl Message for TravelEvent { 167 | type Channel = broadcast::Sender; 168 | } 169 | 170 | impl Message for MainRecv { 171 | type Channel = mpsc::Sender; 172 | } 173 | } 174 | 175 | /// This is the service. 176 | /// The service is a spawnable task that launches from the bus. 177 | /// Service spawn is **synchronous** - the spawn should not send/receive messages, and it should be branchless. 178 | /// This makes errors very predictable. If you take an MPSC receiver twice, you immediately get the error on startup. 179 | mod service { 180 | use super::bus::StateBus; 181 | use crate::{ 182 | message::{MainRecv, TravelEvent}, 183 | state::{SkyState, WeatherState}, 184 | }; 185 | use lifeline::prelude::*; 186 | use postage::{sink::Sink, stream::Stream}; 187 | 188 | pub struct MainService { 189 | _greet: Lifeline, 190 | } 191 | 192 | impl Service for MainService { 193 | type Bus = StateBus; 194 | type Lifeline = anyhow::Result; 195 | 196 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 197 | let mut rx = bus.rx::()?; 198 | let mut tx = bus.tx::()?; 199 | 200 | let _greet = Self::try_task("greet", async move { 201 | while let Some(recv) = rx.recv().await { 202 | match recv { 203 | MainRecv::Travel(location) => { 204 | tx.send(TravelEvent(location)).await?; 205 | } 206 | } 207 | } 208 | 209 | Ok(()) 210 | }); 211 | 212 | Ok(Self { _greet }) 213 | } 214 | } 215 | 216 | // The state service keeps track of the state 217 | pub struct StateService { 218 | _travel: Lifeline, 219 | } 220 | 221 | impl Service for StateService { 222 | type Bus = StateBus; 223 | type Lifeline = anyhow::Result; 224 | 225 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 226 | let mut rx = bus.rx::()?; 227 | 228 | // if you need to get to the original channel, you can use into_inner() 229 | // this will make your code more fragile when you change the types in the bus, though! 230 | let mut tx = bus.tx::()?; 231 | 232 | let _travel = Self::try_task("travel", async move { 233 | // default is nice! we can initialize to the same value as the tx stores! 234 | let mut state = SkyState::default(); 235 | 236 | // there is a small bug here w/ broadcast lagged error. 237 | // ignoring it for simplicity :D 238 | while let Some(update) = rx.recv().await { 239 | state.location = update.0; 240 | match state.location { 241 | crate::state::LocationState::None => state.weather = WeatherState::None, 242 | crate::state::LocationState::Boston => { 243 | state.weather = WeatherState::Snowing 244 | } 245 | crate::state::LocationState::SanDiego => { 246 | state.weather = WeatherState::Sunny72Degrees 247 | } 248 | } 249 | 250 | // rx/tx errors should stop the task! 251 | // this is actually useful - disconnected channels propagate shutdowns. 252 | // in this case, if all the receivers have disconnected, 253 | // we can stop calculating the state. 254 | tx.send(state.clone()).await?; 255 | } 256 | 257 | Ok(()) 258 | }); 259 | 260 | Ok(Self { _travel }) 261 | } 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lifeline-rs 2 | Lifeline is a dependency injection library for message-based applications. Lifeline produces applications which are: 3 | - **Clean:** Bus implementations provide a high-level overview of the application, and services clearly define the messages they send and receive. 4 | - **Decoupled:** Services and tasks have no dependency on their peers, as they only depend on the message types they send and receive. 5 | - **Stoppable:** Services and tasks are trivially cancellable. For example, you can terminate all tasks associated with a connection when a client disconnects. 6 | - **Greppable:** The impact/reach of a message can be easily understood by searching for the type in the source code. 7 | - **Testable:** Lifeline applications communicate via messages, which makes unit testing easy. Create the bus, spawn the service, send a message, and expect an output message. 8 | 9 | In order to achieve these goals, lifeline provides patterns, traits, and implementations: 10 | - The **Bus**, which constructs & distributes channel Senders/Receivers, and Resources. 11 | - The **Carrier**, which translates messages between two Bus instances. Carriers are critical when building large applications, and help minimize the complexity of the messages on each bus. 12 | - The **Service**, which takes channels from the bus, and spawns tasks which send and receive messages. 13 | - The **Task**, an async future which returns a lifeline when spawned. When the lifeline is dropped, the future is immedately cancelled. 14 | - The **Resource**, a struct which can be stored in the bus, and taken (or cloned) when services spawn. 15 | 16 | For a quick introduction, see the [hello.rs example.](https://github.com/austinjones/lifeline-rs/blob/master/examples/hello.rs) 17 | For a full-scale application see [tab-rs.](https://github.com/austinjones/tab-rs) 18 | 19 | ## Quickstart 20 | Lifeline can be used with the [tokio](https://docs.rs/tokio/) and [async-std](https://docs.rs/async-std/) runtimes. By default, lifeline uses `tokio`. 21 | ```toml 22 | lifeline = "0.6" 23 | ``` 24 | 25 | [async-std](https://docs.rs/async-std/) can be enabled with the `async-std-executor` feature. And the `mpsc` implementation can be enabled with the `async-std-channels` feature: 26 | ```toml 27 | lifeline = { version = "0.6", default-features = false, features = ["dyn-bus", "async-std-executor", "async-std-channels"] } 28 | ``` 29 | 30 | Lifeline also supports [postage channels](https://docs.rs/postage/), a library that provides a portable set of channel implementations (compatible with any executor). 31 | Postage also provides Stream and Sink combinators (similar to futures StreamExt), that are optimized for async channels. 32 | Postage is intended to replace the LifelineSender/LifelineReceiver wrappers that were removed in lifeline v0.6.0. 33 | 34 | ## Upgrading 35 | v0.6.0 contains several breaking changes: 36 | - The LifelineSender and LifelineReceiver wrappers were removed. This was necessary due to the recent changes in the Stream ecosystem, and the upcoming stabilization of the Stream RFC. 37 | If you need Stream/Sink combinators, take a look at [postage](https://crates.io/crates/postage), or [tokio-stream](https://crates.io/crates/tokio-stream). 38 | - The barrier channel was removed. It can be replaced with [postage::barrier](https://docs.rs/postage/0.3.1/postage/barrier/index.html). 39 | - The subscription channel was removed. If you need it back, you can find the code before the removal [here](https://github.com/austinjones/lifeline-rs/blob/b15ab2342abcfa9c553d403cb58d2403531bf89c/src/channel/subscription.rs). 40 | - The Sender and Receiver traits were removed from prelude. This is so that importing the lifeline prelude does not conflict with Sink/Stream traits. You can import them with: 41 | `use lifeline::{Sender, Receiver}`. 42 | 43 | ## The Bus 44 | The bus carries channels and resources. When services spawn, they receive a reference to the bus. 45 | 46 | Channels can be taken from the bus. If the channel endpoint is clonable, it will remain available for other services. If the channel is not clonable, future calls will receive an `Err` value. The Rx/Tx type parameters are type-safe, and will produce a compile error 47 | if you attempt to take a channel for an message type which the bus does not carry. 48 | 49 | ### Bus Example 50 | ```rust 51 | use lifeline::lifeline_bus; 52 | use lifeline::Message; 53 | use lifeline::Bus; 54 | use myapp::message::MainSend; 55 | use myapp::message::MainRecv; 56 | use tokio::sync::mpsc; 57 | 58 | lifeline_bus!(pub struct MainBus); 59 | 60 | impl Message for MainSend { 61 | type Channel = mpsc::Sender; 62 | } 63 | 64 | impl Message for MainRecv { 65 | type Channel = mpsc::Sender; 66 | } 67 | 68 | fn use_bus() -> anyhow::Result<()> { 69 | let bus = MainBus::default(); 70 | 71 | let tx_main = bus.tx::()?; 72 | tx_main.send(MainRecv {}); 73 | 74 | Ok(()) 75 | } 76 | ``` 77 | 78 | ## The Carrier 79 | Carriers provide a way to move messages between busses. Carriers can translate, ignore, or collect information, 80 | providing each bus with the messages that it needs. 81 | 82 | Large applications have a tree of Busses. This is good, it breaks your app into small chunks. 83 | ``` 84 | - MainBus 85 | | ConnectionListenerBus 86 | | | ConnectionBus 87 | | DomainSpecificBus 88 | | | ... 89 | ``` 90 | 91 | Carriers allow each bus to define messages that minimally represent the information it's services need to function, and prevent an explosion of messages which are copied to all busses. 92 | 93 | Carriers centralize the communication between busses, making large applications easier to reason about. 94 | 95 | ### Carrier Example 96 | Busses deeper in the tree should implement `FromCarrier` for their parents - see the [carrier.rs example](https://github.com/austinjones/lifeline-rs/blob/master/examples/carrier.rs) for more details. 97 | 98 | ```rust 99 | let main_bus = MainBus::default(); 100 | let connection_listener_bus = ConnectionListenerBus::default(); 101 | let _carrier = connection_listener_bus.carry_from(&main_bus)?; 102 | // you can also use the IntoCarrier trait, which has a blanket implementation 103 | let _carrier = main_bus.carry_into(&main_bus)?; 104 | ``` 105 | 106 | ## The Service 107 | The Service synchronously takes channels from the Bus, and spawns a tree of async tasks (which send & receive messages). When spawned, the service returns one or more Lifeline values. When a Lifeline is dropped, the associated task is immediately cancelled. 108 | 109 | It's common for `Service::spawn` to return a Result. Taking channel endpoints is a fallible operation. This is because, depending on the channel type, the endpoint may not be clonable. Lifeline clones endpoints when it can (`mpsc::Sender`, `broadcast::*`, and `watch::Receiver`). Lifeline tries to make this happen as early as possible. 110 | 111 | ```rust 112 | use lifeline::{Service, Task}; 113 | pub struct ExampleService { 114 | _greet: Lifeline, 115 | } 116 | 117 | impl Service for ExampleService { 118 | type Bus = ExampleBus; 119 | type Lifeline = Lifeline; 120 | 121 | fn spawn(bus: &Self::Bus) -> Self::Lifeline { 122 | let mut rx = bus.rx::()?; 123 | let mut tx = bus.tx::()?; 124 | let config = bus.resource::()?; 125 | 126 | Self::task("greet", async move { 127 | // drive the channels! 128 | }) 129 | } 130 | } 131 | ``` 132 | 133 | ## The Task 134 | The Task executes a `Future`, and returns a `Lifeline` value when spawned. When the lifeline is dropped, the future is immediately cancelled. 135 | 136 | `Task` is a trait that is implemented for all types - you can import it and use `Self::task` in any type. In lifeline, it's 137 | most commonly used in Service implementations. 138 | 139 | Tasks can be infallible: 140 | ```rust 141 | Self::task("greet", async move { 142 | // do something 143 | }) 144 | ``` 145 | 146 | Or, if you have a fallible task, you can return `anyhow::Result`. Anyhow is required to solve type inference issues. 147 | 148 | ```rust 149 | Self::try_task("greet", async move { 150 | // do something 151 | let something = do_something()?; 152 | Ok(()) 153 | }) 154 | ``` 155 | 156 | # Testing 157 | One of the goals of Lifeline is to provide interfaces that are very easy to test. Lifeline runtimes are easy to construct in tests: 158 | 159 | ```rust 160 | #[tokio::test] 161 | async fn test() -> anyhow::Result<()> { 162 | // this is zero-cost. channel construction is lazy. 163 | let bus = MainBus::default(); 164 | let service = MainService::spawn(&bus)?; 165 | 166 | // the service took `bus.rx::()` 167 | // + `bus.tx::()` 168 | // let's communicate using channels we take. 169 | let tx = bus.tx::()?; 170 | let rx = bus.rx::()?; 171 | 172 | // drop the bus, so that any 'channel closed' errors will occur during our test. 173 | // this would likely happen in practice during the long lifetime of the program 174 | drop(bus); 175 | 176 | tx.send(MainRecv::MyMessage)?; 177 | 178 | // wait up to 200ms for the message to arrive 179 | // if we remove the 200 at the end, the default is 50ms 180 | lifeline::assert_completes!(async move { 181 | let response = rx.recv().await; 182 | assert_eq!(MainSend::MyResponse, response); 183 | }, 200); 184 | 185 | Ok(()) 186 | } 187 | ``` 188 | 189 | # The Details 190 | ### Logging 191 | Tasks (via [log](https://docs.rs/log/0.4.11/log/)) provide debug logs when the are started, ended, or cancelled. 192 | 193 | If the task returns a value, it is also printed to the debug log using `Debug`. 194 | ``` 195 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] START ExampleService/ok_task 196 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] END ExampleService/ok_task 197 | 198 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] START ExampleService/valued_task 199 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] END ExampleService/valued_task: MyStruct {} 200 | ``` 201 | 202 | If the task is cancelled (because it's lifeline is dropped), that is also printed. 203 | ``` 204 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] START ExampleService/cancelled_task 205 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] CANCEL ExampleService/cancelled_task 206 | ``` 207 | 208 | If the task is started using `Task::try_task`, the `Ok`/`Err` value will be printed with `Display`. 209 | 210 | ``` 211 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] START ExampleService/ok_task 212 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] OK ExampleService/ok_task 213 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] END ExampleService/ok_task 214 | 215 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] START ExampleService/err_task 216 | 2020-08-23 16:45:10,422 ERROR [lifeline::service] ERR: ExampleService/err_task: my error 217 | 2020-08-23 16:45:10,422 DEBUG [lifeline::spawn] END ExampleService/err_task 218 | ``` 219 | 220 | ### A note about autocomplete 221 | `rust-analyzer` does not currently support auto-import for structs defined in macros. Lifeline really needs the 222 | struct defined in the macro, as it injects magic fields which store the channels at runtime. 223 | 224 | There is a workaround: define a `prelude.rs` file in your crate root that exports `pub use` for all your bus implementations. 225 | ``` 226 | pub use lifeline::*; 227 | pub use crate::bus::MainBus; 228 | pub use crate::other::OtherBus; 229 | ... 230 | ``` 231 | Then in all your modules: 232 | `use crate::prelude::*` 233 | 234 | ## The Resource 235 | Resources can be stored on the bus. This is very useful for configuration (e.g `MainConfig`), or connections (e.g. a `TcpStream`). 236 | 237 | Resources implement the `Storage` trait, which is easy with the `impl_storage_clone!` and `impl_storage_take!` macros. 238 | 239 | ```rust 240 | use lifeline::{lifeline_bus, impl_storage_clone}; 241 | lifeline_bus!(MainBus); 242 | pub struct MainConfig { 243 | pub port: u16 244 | } 245 | 246 | impl_storage_clone!(MainConfig); 247 | 248 | fn main() { 249 | let bus = MainBus::default() 250 | bus.store_resource::(MainConfig { port: 12345 }); 251 | // from here 252 | } 253 | ``` 254 | 255 | Lifeline does not provide `Resource` implementations for Channel endpoints - use `bus.rx()` and `bus.tx()`. 256 | 257 | ## The Channel 258 | Channel senders must implement the `Channel` trait to be usable in an `impl Message` binding. 259 | 260 | In most cases, the `Channel` endpoints just implement `Storage`, which determines whether to 'take or clone' the endpoint on a `bus.rx()` or `bus.tx()` call. 261 | 262 | Here is an example implem 263 | ```rust 264 | use lifeline::Channel; 265 | use crate::{impl_channel_clone, impl_channel_take}; 266 | use tokio::sync::{broadcast, mpsc, oneshot, watch}; 267 | 268 | impl Channel for mpsc::Sender { 269 | type Tx = Self; 270 | type Rx = mpsc::Receiver; 271 | 272 | fn channel(capacity: usize) -> (Self::Tx, Self::Rx) { 273 | mpsc::channel(capacity) 274 | } 275 | 276 | fn default_capacity() -> usize { 277 | 16 278 | } 279 | } 280 | 281 | impl_channel_clone!(mpsc::Sender); 282 | impl_channel_take!(mpsc::Receiver); 283 | ``` 284 | 285 | Broadcast senders should implement the trait with the `clone_rx` method overriden, to take from `Rx`, then subscribe to `Tx`. 286 | --------------------------------------------------------------------------------