diff --git a/.cargo.config b/.cargo.config new file mode 100644 index 00000000..35049cbc --- /dev/null +++ b/.cargo.config @@ -0,0 +1,2 @@ +[alias] +xtask = "run --package xtask --" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6da48b3c..c4059976 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -88,4 +88,21 @@ jobs: uses: actions-rs/cargo@v1 with: command: doc - args: --lib -r \ No newline at end of file + args: --lib -r + + benches: + name: benches + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@main + - name: Install rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Run benches + uses: actions-rs/cargo@v1 + with: + command: bench + \ No newline at end of file diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 00000000..06d2c7a2 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,45 @@ +name: Code Coverage +on: + push: + branches: + - main + pull_request: + types: [opened, reopened, synchronize] + +jobs: + coverage: + name: Coverage using xtask + strategy: + matrix: + os: [ubuntu-latest] + rust: [stable] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: ${{ matrix.rust }} + override: true + components: llvm-tools-preview + + - uses: Swatinem/rust-cache@v1 + + - name: Download grcov + run: | + mkdir -p "${HOME}/.local/bin" + curl -sL https://github.com/mozilla/grcov/releases/download/v0.8.10/grcov-x86_64-unknown-linux-gnu.tar.bz2 | tar jxf - -C "${HOME}/.local/bin" + echo "$HOME/.local/bin" >> $GITHUB_PATH + - name: Run xtask coverage + uses: actions-rs/cargo@v1 + with: + command: run + args: --bin xtask coverage + + + - name: Upload to codecov.io + uses: codecov/codecov-action@v3 + with: + files: coverage/*.lcov diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index ad2c5de6..3c6ffbaa 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -25,4 +25,5 @@ jobs: - name: Publish crate ractor run: cargo publish --manifest-path Cargo.toml -p ractor env: - CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }} \ No newline at end of file + CARGO_REGISTRY_TOKEN: ${{ secrets.crates_io_token }} + \ No newline at end of file diff --git a/.gitignore b/.gitignore index e3566256..a090c5b1 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,9 @@ Cargo.lock **/*.rs.bk # Remove all target compilation folders from all sub-folders as well -**/target/ \ No newline at end of file +**/target/ + +# Remove code-coverage generated files from git +debug/ +coverage/ +**/*.profraw diff --git a/Cargo.toml b/Cargo.toml index edeb1611..0515a6f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,6 @@ members = [ "ractor", - "ractor-playground" + "ractor-playground", + "xtask" ] diff --git a/ractor-playground/Cargo.toml b/ractor-playground/Cargo.toml index a561c61b..5c1753ca 100644 --- a/ractor-playground/Cargo.toml +++ b/ractor-playground/Cargo.toml @@ -15,4 +15,4 @@ publish = false async-trait = "0.1" ractor = { path="../ractor" } rustyrepl = "0.1" -tokio = { version = "1.23", features = ["full"]} \ No newline at end of file +tokio = { version = "1.23", features = ["full"]} diff --git a/ractor/Cargo.toml b/ractor/Cargo.toml index 63ec90ed..e34af4bd 100644 --- a/ractor/Cargo.toml +++ b/ractor/Cargo.toml @@ -20,5 +20,11 @@ once_cell = "1" tokio = { version = "1.23", features = ["rt", "time", "sync", "macros"]} [dev-dependencies] +criterion = "0.3" function_name = "0.3" -tokio = { version = "1.23", features = ["rt", "time", "sync", "macros", "rt-multi-thread"] } \ No newline at end of file +tokio = { version = "1.23", features = ["rt", "time", "sync", "macros", "rt-multi-thread"] } + +[[bench]] +name = "actor" +harness = false +required-features = [] diff --git a/ractor/benches/actor.rs b/ractor/benches/actor.rs new file mode 100644 index 00000000..8a3398d5 --- /dev/null +++ b/ractor/benches/actor.rs @@ -0,0 +1,188 @@ +// Copyright (c) Sean Lawlor +// +// This source code is licensed under both the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree. + +#[macro_use] +extern crate criterion; + +use criterion::{BatchSize, Criterion}; +use ractor::{Actor, ActorHandler, ActorRef}; + +struct BenchActor; + +#[async_trait::async_trait] +impl ActorHandler for BenchActor { + type Msg = (); + + type State = (); + + async fn pre_start(&self, myself: ActorRef) -> Self::State { + let _ = myself.cast(()); + } + + async fn handle(&self, myself: ActorRef, _message: Self::Msg, _state: &mut Self::State) { + myself.stop(None); + } +} + +fn create_actors(c: &mut Criterion) { + let small = 100; + let large = 10000; + + let id = format!("Creation of {} actors", small); + let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + c.bench_function(&id, move |b| { + b.iter_batched( + || {}, + |()| { + runtime.block_on(async move { + let mut handles = vec![]; + for _ in 0..small { + let (_, handler) = Actor::spawn(None, BenchActor) + .await + .expect("Failed to create test agent"); + handles.push(handler); + } + handles + }) + }, + BatchSize::PerIteration, + ); + }); + + let id = format!("Creation of {} actors", large); + let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + c.bench_function(&id, move |b| { + b.iter_batched( + || {}, + |()| { + runtime.block_on(async move { + let mut handles = vec![]; + for _ in 0..large { + let (_, handler) = Actor::spawn(None, BenchActor) + .await + .expect("Failed to create test agent"); + handles.push(handler); + } + handles + }) + }, + BatchSize::PerIteration, + ); + }); +} + +fn schedule_work(c: &mut Criterion) { + let small = 100; + let large = 1000; + + let id = format!("Waiting on {} actors to process first message", small); + let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + c.bench_function(&id, move |b| { + b.iter_batched( + || { + runtime.block_on(async move { + let mut handles = vec![]; + for _ in 0..small { + let (_, handler) = Actor::spawn(None, BenchActor) + .await + .expect("Failed to create test agent"); + handles.push(handler); + } + handles + }) + }, + |handles| { + runtime.block_on(async move { + let _ = futures::future::join_all(handles).await; + }) + }, + BatchSize::PerIteration, + ); + }); + + let id = format!("Waiting on {} actors to process first message", large); + let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + c.bench_function(&id, move |b| { + b.iter_batched( + || { + runtime.block_on(async move { + let mut handles = vec![]; + for _ in 0..large { + let (_, handler) = Actor::spawn(None, BenchActor) + .await + .expect("Failed to create test agent"); + handles.push(handler); + } + handles + }) + }, + |handles| { + runtime.block_on(async move { + let _ = futures::future::join_all(handles).await; + }) + }, + BatchSize::PerIteration, + ); + }); +} + +#[allow(clippy::async_yields_async)] +fn process_messages(c: &mut Criterion) { + const NUM_MSGS: u64 = 100000; + + struct MessagingActor { + num_msgs: u64, + } + + #[async_trait::async_trait] + impl ActorHandler for MessagingActor { + type Msg = (); + + type State = u64; + + async fn pre_start(&self, myself: ActorRef) -> Self::State { + let _ = myself.cast(()); + 0u64 + } + + async fn handle( + &self, + myself: ActorRef, + _message: Self::Msg, + state: &mut Self::State, + ) { + *state += 1; + if *state >= self.num_msgs { + myself.stop(None); + } else { + let _ = myself.cast(()); + } + } + } + + let id = format!("Waiting on {} messages to be processed", NUM_MSGS); + let runtime = tokio::runtime::Builder::new_multi_thread().build().unwrap(); + c.bench_function(&id, move |b| { + b.iter_batched( + || { + runtime.block_on(async move { + let (_, handle) = Actor::spawn(None, MessagingActor { num_msgs: NUM_MSGS }) + .await + .expect("Failed to create test actor"); + handle + }) + }, + |handle| { + runtime.block_on(async move { + let _ = handle.await; + }) + }, + BatchSize::PerIteration, + ); + }); +} + +criterion_group!(actors, create_actors, schedule_work, process_messages); +criterion_main!(actors); diff --git a/ractor/examples/supervisor.rs b/ractor/examples/supervisor.rs index 16548cdd..04b2c709 100644 --- a/ractor/examples/supervisor.rs +++ b/ractor/examples/supervisor.rs @@ -58,6 +58,7 @@ async fn main() { struct LeafActor; +#[derive(Clone)] struct LeafActorState {} enum LeafActorMessage { @@ -106,6 +107,7 @@ impl ActorHandler for LeafActor { struct MidLevelActor; +#[derive(Clone)] struct MidLevelActorState { leaf_actor: ActorRef, } @@ -177,6 +179,7 @@ impl ActorHandler for MidLevelActor { struct RootActor; +#[derive(Clone)] struct RootActorState { mid_level_actor: ActorRef, } diff --git a/ractor/src/actor/actor_cell/actor_ref.rs b/ractor/src/actor/actor_cell/actor_ref.rs index 5339c219..070905f2 100644 --- a/ractor/src/actor/actor_cell/actor_ref.rs +++ b/ractor/src/actor/actor_cell/actor_ref.rs @@ -97,8 +97,8 @@ where /// Notify the supervisors that a supervision event occurred /// /// * `evt` - The event to send to this [crate::Actor]'s supervisors - pub fn notify_supervisors(&self, evt: SupervisionEvent) { - self.inner.notify_supervisors::(evt) + pub fn notify_supervisor(&self, evt: SupervisionEvent) { + self.inner.notify_supervisor::(evt) } // ========================== General Actor Operation Aliases ========================== // diff --git a/ractor/src/actor/actor_cell/mod.rs b/ractor/src/actor/actor_cell/mod.rs index 96e99f07..c8d915f3 100644 --- a/ractor/src/actor/actor_cell/mod.rs +++ b/ractor/src/actor/actor_cell/mod.rs @@ -226,23 +226,26 @@ impl ActorCell { } // notify children they should die. They will unlink themselves from the supervisor - self.inner.tree.terminate_children(); + self.inner.tree.terminate_all_children(); } - /// Link this [super::Actor] to the supervisor + /// Link this [super::Actor] to the provided supervisor /// - /// * `supervisor` - The supervisor to link this [super::Actor] to + /// * `supervisor` - The child to link this [super::Actor] to pub fn link(&self, supervisor: ActorCell) { - supervisor.inner.tree.insert_parent(self.clone()); - self.inner.tree.insert_child(supervisor); + supervisor.inner.tree.insert_child(self.clone()); + self.inner.tree.set_supervisor(supervisor); } - /// Unlink this [super::Actor] from the supervisor + /// Unlink this [super::Actor] from the supervisor if it's + /// currently linked (if self's supervisor is `supervisor`) /// - /// * `supervisor` - The supervisor to unlink this [super::Actor] from + /// * `supervisor` - The child to unlink this [super::Actor] from pub fn unlink(&self, supervisor: ActorCell) { - supervisor.inner.tree.remove_parent(self.clone()); - self.inner.tree.remove_child(supervisor); + if self.inner.tree.is_child_of(supervisor.get_id()) { + supervisor.inner.tree.remove_child(self.get_id()); + self.inner.tree.clear_supervisor(); + } } /// Kill this [super::Actor] forcefully (terminates async work) @@ -289,11 +292,11 @@ impl ActorCell { /// Notify the supervisors that a supervision event occurred /// /// * `evt` - The event to send to this [super::Actor]'s supervisors - pub fn notify_supervisors(&self, evt: SupervisionEvent) + pub fn notify_supervisor(&self, evt: SupervisionEvent) where TActor: ActorHandler, { - self.inner.tree.notify_supervisors::(evt) + self.inner.tree.notify_supervisor::(evt) } // ================== Test Utilities ================== // diff --git a/ractor/src/actor/messages.rs b/ractor/src/actor/messages.rs index 0e0ef3ab..79d6372f 100644 --- a/ractor/src/actor/messages.rs +++ b/ractor/src/actor/messages.rs @@ -11,7 +11,6 @@ use std::any::Any; use std::fmt::Debug; -use std::sync::Arc; use crate::{Message, State}; @@ -31,7 +30,7 @@ impl BoxedMessage { /// Create a new [BoxedMessage] from a strongly-typed message pub fn new(msg: T) -> Self where - T: Any + Message, + T: Message, { Self { msg: Some(Box::new(msg)), @@ -42,7 +41,7 @@ impl BoxedMessage { /// the boxed message pub fn take(&mut self) -> Result where - T: Any + Message, + T: Message, { match self.msg.take() { Some(m) => { @@ -57,57 +56,44 @@ impl BoxedMessage { } } -/// A "boxed" state denoting a strong-type state +/// A "boxed" message denoting a strong-type message /// but generic so it can be passed around without type -/// constraints. -/// -/// It is a shared [Arc] to the last [crate::ActorHandler::State] -/// that the actor reported. This means, that the state shouldn't -/// be mutated once the actor is dead, it should be generally -/// immutable since a shared [Arc] will be sent to every supervisor +/// constraints pub struct BoxedState { - /// The state value - pub state: Box, -} - -impl Debug for BoxedState { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "BoxedState") - } + /// The message value + pub msg: Option>, } impl BoxedState { - /// Create a new [BoxedState] from a strongly-typed state + /// Create a new [BoxedState] from a strongly-typed message pub fn new(msg: T) -> Self where T: State, { Self { - state: Box::new(Arc::new(msg)), + msg: Some(Box::new(msg)), } } - /// Try and take the resulting type via cloning, such that we don't - /// consume the value - pub fn take(&self) -> Result, BoxedDowncastErr> + /// Try and take the resulting message as a specific type, consumes + /// the boxed message + pub fn take(&mut self) -> Result where T: State, { - let state_ref = &self.state; - if state_ref.is::>() { - Ok(state_ref.downcast_ref::>().cloned().unwrap()) - } else { - Err(BoxedDowncastErr) + match self.msg.take() { + Some(m) => { + if m.is::() { + Ok(*m.downcast::().unwrap()) + } else { + Err(BoxedDowncastErr) + } + } + None => Err(BoxedDowncastErr), } } } -impl Debug for BoxedMessage { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("BoxedMessage") - } -} - /// Messages to stop an actor pub(crate) enum StopMessage { // Normal stop @@ -180,37 +166,6 @@ impl std::fmt::Display for SupervisionEvent { } } -impl SupervisionEvent { - /// A custom "clone" for [SupervisionEvent]s, because they hold a handle to a [BoxedState] - /// which can't directly implement the clone trait, therefore the [SupervisionEvent] - /// can't implement [Clone]. This requires that you give the intended strongly typed [State] - /// which will downcast, clone the underlying type, and create a new boxed state for you - pub(crate) fn duplicate(&self) -> Result - where - TState: State, - { - match self { - Self::ActorStarted(actor) => Ok(Self::ActorStarted(actor.clone())), - Self::ActorTerminated(actor, maybe_state, maybe_exit_reason) => { - let cloned_maybe_state = match maybe_state { - Some(state) => Some(state.take::()?), - _ => None, - } - .map(BoxedState::new); - Ok(Self::ActorTerminated( - actor.clone(), - cloned_maybe_state, - maybe_exit_reason.clone(), - )) - } - Self::ActorPanicked(actor, message) => { - Ok(Self::ActorPanicked(actor.clone(), message.clone())) - } - Self::ProcessGroupChanged(change) => Ok(Self::ProcessGroupChanged(change.clone())), - } - } -} - /// A signal message which takes priority above all else #[derive(Clone)] pub enum Signal { diff --git a/ractor/src/actor/mod.rs b/ractor/src/actor/mod.rs index bb42cd4e..cf73edc1 100644 --- a/ractor/src/actor/mod.rs +++ b/ractor/src/actor/mod.rs @@ -208,7 +208,7 @@ where // setup supervision if let Some(sup) = &supervisor { - sup.link(self.base.clone().into()); + self.base.link(sup.clone()); } // run the processing loop, backgrounding the work @@ -244,14 +244,14 @@ where myself.terminate(); // notify supervisors of the actor's death - myself.notify_supervisors(evt); + myself.notify_supervisor(evt); // set status to stopped myself.set_status(ActorStatus::Stopped); // unlink superisors if let Some(sup) = supervisor { - sup.unlink(myself.clone().into()); + myself.unlink(sup); } }); @@ -268,7 +268,7 @@ where Self::do_post_start(myself.clone(), handler.clone(), state).await?; myself.set_status(ActorStatus::Running); - myself.notify_supervisors(SupervisionEvent::ActorStarted(myself.clone().into())); + myself.notify_supervisor(SupervisionEvent::ActorStarted(myself.clone().into())); let myself_clone = myself.clone(); let handler_clone = handler.clone(); diff --git a/ractor/src/actor/supervision.rs b/ractor/src/actor/supervision.rs index 3665b0b1..539c1b14 100644 --- a/ractor/src/actor/supervision.rs +++ b/ractor/src/actor/supervision.rs @@ -15,6 +15,11 @@ //! which will be expanded upon as the library develops. Next in line is likely supervision strategies //! for automatic restart routines. +use std::sync::{ + atomic::{AtomicU64, Ordering}, + Arc, RwLock, +}; + use dashmap::DashMap; use super::{actor_cell::ActorCell, messages::SupervisionEvent}; @@ -23,20 +28,21 @@ use crate::{ActorHandler, ActorId}; /// A supervision tree #[derive(Default)] pub struct SupervisionTree { - children: DashMap, - parents: DashMap, + children: DashMap, + supervisor: Arc>>, + start_order: AtomicU64, } impl SupervisionTree { /// Push a child into the tere pub fn insert_child(&self, child: ActorCell) { - self.children.insert(child.get_id(), child); + let start_order = self.start_order.fetch_add(1, Ordering::Relaxed); + self.children.insert(child.get_id(), (start_order, child)); } /// Remove a specific actor from the supervision tree (e.g. actor died) - pub fn remove_child(&self, child: ActorCell) { - let id = child.get_id(); - match self.children.entry(id) { + pub fn remove_child(&self, child: ActorId) { + match self.children.entry(child) { dashmap::mapref::entry::Entry::Occupied(item) => { item.remove(); } @@ -45,56 +51,78 @@ impl SupervisionTree { } /// Push a parent into the tere - pub fn insert_parent(&self, parent: ActorCell) { - self.parents.insert(parent.get_id(), parent); + pub fn set_supervisor(&self, parent: ActorCell) { + *(self.supervisor.write().unwrap()) = Some(parent); } /// Remove a specific actor from the supervision tree (e.g. actor died) - pub fn remove_parent(&self, parent: ActorCell) { - let id = parent.get_id(); - match self.parents.entry(id) { - dashmap::mapref::entry::Entry::Occupied(item) => { - item.remove(); - } - dashmap::mapref::entry::Entry::Vacant(_) => {} - } + pub fn clear_supervisor(&self) { + *(self.supervisor.write().unwrap()) = None; } /// Terminate all your supervised children - pub fn terminate_children(&self) { + pub fn terminate_all_children(&self) { for kvp in self.children.iter() { - kvp.value().terminate(); + kvp.value().1.terminate(); } } - /// Determine if the specified actor is a member of this supervision tree - pub fn is_supervisor_of(&self, id: ActorId) -> bool { - self.children.contains_key(&id) + /// Terminate the supervised children after a given actor (including the specified actor). + /// This is necessary to support [Erlang's supervision model](https://www.erlang.org/doc/design_principles/sup_princ.html#flags), + /// specifically the `rest_for_one` strategy + /// + /// * `id` - The id of the actor to terminate + all those that follow + pub fn terminate_children_after(&self, id: ActorId) { + let mut reference_point = u64::MAX; + let mut id_map = std::collections::HashMap::new(); + + // keep the lock inside this scope on the map + { + for item in self.children.iter_mut() { + id_map.insert(item.value().0, *item.key()); + if item.value().1.get_id() == id { + reference_point = item.value().0; + break; + } + } + } + + // if there was a reference point, terminate children from that point on + if reference_point < u64::MAX { + for child in self.children.iter() { + child.1.terminate(); + } + } } /// Determine if the specified actor is a parent of this actor pub fn is_child_of(&self, id: ActorId) -> bool { - self.parents.contains_key(&id) + if let Some(parent) = &*(self.supervisor.read().unwrap()) { + parent.get_id() == id + } else { + false + } } /// Send a notification to all supervisors - pub fn notify_supervisors(&self, evt: SupervisionEvent) + pub fn notify_supervisor(&self, evt: SupervisionEvent) where TActor: ActorHandler, { - for kvp in self.parents.iter() { - let evt_clone = evt.duplicate::().unwrap(); - let _ = kvp.value().send_supervisor_evt(evt_clone); + if let Some(parent) = &*(self.supervisor.read().unwrap()) { + let _ = parent.send_supervisor_evt(evt); } } /// Retrieve the number of supervised children + #[cfg(test)] pub fn get_num_children(&self) -> usize { self.children.len() } /// Retrieve the number of supervised children + #[cfg(test)] pub fn get_num_parents(&self) -> usize { - self.parents.len() + usize::from(self.supervisor.read().unwrap().is_some()) } } diff --git a/ractor/src/actor/tests/supervisor.rs b/ractor/src/actor/tests/supervisor.rs index dc1397c6..bcad227a 100644 --- a/ractor/src/actor/tests/supervisor.rs +++ b/ractor/src/actor/tests/supervisor.rs @@ -152,7 +152,8 @@ async fn test_supervision_panic_in_handle() { // trigger the child failure child_ref.send_message(()).expect("Failed to send message"); - let (_, _) = tokio::join!(s_handle, c_handle); + let _ = s_handle.await; + let _ = c_handle.await; assert_eq!(child_ref.get_id().get_pid(), flag.load(Ordering::Relaxed)); @@ -171,9 +172,9 @@ async fn test_supervision_panic_in_post_stop() { impl ActorHandler for Child { type Msg = (); type State = (); - async fn pre_start(&self, this_actor: ActorRef) -> Self::State { + async fn pre_start(&self, myself: ActorRef) -> Self::State { // trigger stop, which starts shutdown - this_actor.stop(None); + myself.stop(None); } async fn post_stop(&self, _this_actor: ActorRef, _state: &mut Self::State) { panic!("Boom"); @@ -185,14 +186,6 @@ async fn test_supervision_panic_in_post_stop() { type Msg = (); type State = (); async fn pre_start(&self, _this_actor: ActorRef) -> Self::State {} - async fn handle( - &self, - _this_actor: ActorRef, - _message: Self::Msg, - _state: &mut Self::State, - ) { - } - async fn handle_supervisor_evt( &self, this_actor: ActorRef, @@ -216,13 +209,12 @@ async fn test_supervision_panic_in_post_stop() { .await .expect("Supervisor panicked on startup"); - let supervisor_cell: ActorCell = supervisor_ref.clone().into(); - - let (child_ref, c_handle) = Actor::spawn_linked(None, Child, supervisor_cell) + let (child_ref, c_handle) = Actor::spawn_linked(None, Child, supervisor_ref.clone().into()) .await .expect("Child panicked on startup"); - let (_, _) = tokio::join!(s_handle, c_handle); + let _ = s_handle.await; + let _ = c_handle.await; assert_eq!(child_ref.get_id().get_pid(), flag.load(Ordering::Relaxed)); @@ -403,3 +395,6 @@ async fn test_killing_a_supervisor_terminates_children() { c_handle.await.expect("Failed to wait for child to die"); } + +// TODO: Still to be tested +// 1. terminate_children_after() diff --git a/ractor/src/lib.rs b/ractor/src/lib.rs index 4b3cfb95..0819e359 100644 --- a/ractor/src/lib.rs +++ b/ractor/src/lib.rs @@ -151,6 +151,9 @@ pub mod registry; pub mod rpc; pub mod time; +#[cfg(test)] +use criterion as _; + // WIP // #[cfg(feature = "remote")] // pub mod distributed; @@ -177,13 +180,13 @@ pub use port::{InputPort, OutputMessage, OutputPort, RpcReplyPort}; /// PrintName, /// } /// ``` -pub trait Message: Send + 'static {} -impl Message for T {} +pub trait Message: Any + Send + 'static {} +impl Message for T {} /// Represents the state of an actor. Must be safe /// to send between threads -pub trait State: Any + Sync + Send + 'static {} -impl State for T {} +pub trait State: Message {} +impl State for T {} /// Error types which can result from Ractor processes #[derive(Debug)] diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml new file mode 100644 index 00000000..6cc4cdcf --- /dev/null +++ b/xtask/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xtask" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +xtaskops = "0.4" +anyhow = "1" diff --git a/xtask/README.md b/xtask/README.md new file mode 100644 index 00000000..b3ebfdb8 --- /dev/null +++ b/xtask/README.md @@ -0,0 +1,13 @@ +This package is included here to support the automatic reporting of code coverage on +Github. To view code coverage locally: + +Do this once to set it up: +``` +rustup component add llvm-tools-preview +cargo install grcov +``` + +Subsequently, run: +``` +cargo xtask coverage --dev +``` \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs new file mode 100644 index 00000000..c5a4a793 --- /dev/null +++ b/xtask/src/main.rs @@ -0,0 +1,8 @@ +// Copyright (c) Sean Lawlor +// +// This source code is licensed under both the MIT license found in the +// LICENSE-MIT file in the root directory of this source tree. + +fn main() -> Result<(), anyhow::Error> { + xtaskops::tasks::main() +}