├── benches ├── release.toml ├── messaging_mi.rs ├── messaging_sys.rs ├── messaging_tc.rs ├── messaging_je.rs ├── trace_id.rs ├── Cargo.toml ├── stream.rs ├── yield.rs ├── coop.rs └── common.rs ├── examples ├── release.toml ├── network │ ├── protocol.rs │ ├── main.rs │ ├── bob.toml │ ├── alice.toml │ ├── bob.rs │ └── alice.rs ├── README.md ├── stream.rs ├── Cargo.toml ├── usage │ └── config.toml ├── test.rs └── tokio-broadcast.rs ├── elfo-core ├── src │ ├── telemetry │ │ ├── mod.rs │ │ └── config.rs │ ├── restarting │ │ ├── mod.rs │ │ └── config.rs │ ├── logging │ │ ├── mod.rs │ │ ├── control.rs │ │ └── config.rs │ ├── time │ │ └── mod.rs │ ├── dumping │ │ ├── recorder.rs │ │ ├── config.rs │ │ ├── dumper.rs │ │ ├── sequence_no.rs │ │ ├── mod.rs │ │ ├── raw.rs │ │ └── control.rs │ ├── tracing │ │ ├── mod.rs │ │ └── validator.rs │ ├── thread.rs │ ├── panic.rs │ ├── supervisor │ │ ├── error_chain.rs │ │ └── measure_poll.rs │ ├── demux.rs │ ├── runtime.rs │ ├── macros.rs │ ├── exec.rs │ ├── subscription.rs │ ├── routers │ │ └── map.rs │ ├── remote.rs │ ├── lib.rs │ ├── message │ │ └── protocol.rs │ ├── context │ │ └── stats.rs │ └── stuck_detection.rs └── Cargo.toml ├── .gitignore ├── release.toml ├── elfo-network ├── src │ ├── frame │ │ ├── mod.rs │ │ └── buffers.rs │ ├── connman │ │ └── config.rs │ ├── discovery │ │ ├── advise_timer.rs │ │ └── diff.rs │ ├── rtt.rs │ ├── socket │ │ ├── idleness.rs │ │ ├── raw │ │ │ └── turmoil.rs │ │ └── capabilities │ │ │ └── mod.rs │ ├── worker │ │ └── requests.rs │ ├── node_map.rs │ └── lib.rs └── Cargo.toml ├── elfo ├── tests │ ├── ui.rs │ ├── ui │ │ ├── msg_double_wild.stderr │ │ ├── msg_unused_token.stderr │ │ ├── msg_double_wild.rs │ │ ├── msg_request_syntax_for_regular.rs │ │ ├── msg_unused_token.rs │ │ ├── msg_regular_syntax_for_request.rs │ │ ├── msg_invalid_pattern.rs │ │ ├── msg_regular_syntax_for_request.stderr │ │ ├── msg_invalid_pattern.stderr │ │ └── msg_request_syntax_for_regular.stderr │ ├── common.rs │ ├── start_info.rs │ ├── gentle_outcome.rs │ ├── mailbox_capacity.rs │ ├── source_signal.rs │ ├── update_config.rs │ └── source_delay.rs ├── release.toml ├── src │ └── lib.rs └── Cargo.toml ├── rustfmt.toml ├── elfo-test ├── src │ ├── lib.rs │ └── utils.rs └── Cargo.toml ├── elfo-macros-impl ├── src │ ├── lib.rs │ └── errors.rs └── Cargo.toml ├── elfo-telemeter ├── src │ ├── metrics │ │ ├── counter.rs │ │ ├── mod.rs │ │ └── histogram.rs │ ├── render.rs │ ├── lib.rs │ ├── recorder.rs │ ├── allocator.rs │ └── config.rs ├── Cargo.toml └── tests │ └── smoke.rs ├── elfo-utils ├── src │ ├── likely.rs │ ├── time │ │ ├── mod.rs │ │ └── system.rs │ └── lib.rs ├── Cargo.toml └── benches │ └── rate_limiter.rs ├── elfo-macros ├── Cargo.toml └── src │ └── lib.rs ├── elfo-pinger ├── Cargo.toml └── src │ ├── lib.rs │ ├── config.rs │ └── actor.rs ├── elfo-dumper ├── src │ ├── recorder.rs │ └── lib.rs ├── Cargo.toml └── tests │ └── smoke.rs ├── elfo-configurer ├── Cargo.toml └── src │ └── protocol.rs ├── elfo-logger ├── src │ ├── stats.rs │ ├── theme.rs │ ├── line_transaction.rs │ └── printing_layer │ │ └── mod.rs ├── Cargo.toml └── tests │ └── smoke.rs ├── README.md ├── Cargo.toml └── .github └── workflows └── ci.yml /benches/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /examples/release.toml: -------------------------------------------------------------------------------- 1 | release = false 2 | -------------------------------------------------------------------------------- /elfo-core/src/telemetry/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **/target 2 | .idea 3 | Cargo.lock 4 | 5 | *.dump 6 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-commit-message = "chore: release" 2 | allow-branch = ["master"] 3 | -------------------------------------------------------------------------------- /elfo-network/src/frame/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod buffers; 2 | pub(crate) mod lz4; 3 | pub(crate) mod read; 4 | pub(crate) mod write; 5 | -------------------------------------------------------------------------------- /elfo/tests/ui.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | #[test] 4 | fn ui() { 5 | let t = trybuild::TestCases::new(); 6 | t.compile_fail("tests/ui/*.rs"); 7 | } 8 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_double_wild.stderr: -------------------------------------------------------------------------------- 1 | error: this branch will never be matched 2 | --> tests/ui/msg_double_wild.rs:7:9 3 | | 4 | 7 | b => {} 5 | | ^ 6 | -------------------------------------------------------------------------------- /examples/network/protocol.rs: -------------------------------------------------------------------------------- 1 | use elfo::prelude::*; 2 | 3 | #[message(ret = String)] 4 | pub struct AskName(pub u32); 5 | 6 | #[message] 7 | pub struct Hello(pub u32); 8 | -------------------------------------------------------------------------------- /elfo-network/src/connman/config.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | #[derive(Debug)] 4 | pub(crate) struct Config { 5 | pub(crate) reconnect_interval: Duration, 6 | } 7 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | merge_derives = false 3 | imports_granularity = "Crate" 4 | normalize_comments = true 5 | reorder_impl_items = true 6 | wrap_comments = true 7 | -------------------------------------------------------------------------------- /benches/messaging_mi.rs: -------------------------------------------------------------------------------- 1 | use mimalloc::MiMalloc; 2 | 3 | mod messaging; 4 | 5 | #[global_allocator] 6 | static ALLOCATOR: MiMalloc = MiMalloc; 7 | 8 | criterion::criterion_main!(messaging::cases); 9 | -------------------------------------------------------------------------------- /benches/messaging_sys.rs: -------------------------------------------------------------------------------- 1 | use std::alloc::System; 2 | 3 | mod messaging; 4 | 5 | #[global_allocator] 6 | static ALLOCATOR: System = System; 7 | 8 | criterion::criterion_main!(messaging::cases); 9 | -------------------------------------------------------------------------------- /benches/messaging_tc.rs: -------------------------------------------------------------------------------- 1 | use tcmalloc::TCMalloc; 2 | 3 | mod messaging; 4 | 5 | #[global_allocator] 6 | static ALLOCATOR: TCMalloc = TCMalloc; 7 | 8 | criterion::criterion_main!(messaging::cases); 9 | -------------------------------------------------------------------------------- /benches/messaging_je.rs: -------------------------------------------------------------------------------- 1 | use jemallocator::Jemalloc; 2 | 3 | mod messaging; 4 | 5 | #[global_allocator] 6 | static ALLOCATOR: Jemalloc = Jemalloc; 7 | 8 | criterion::criterion_main!(messaging::cases); 9 | -------------------------------------------------------------------------------- /elfo-core/src/restarting/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | 3 | mod backoff; 4 | mod restart_policy; 5 | 6 | pub(crate) use self::backoff::RestartBackoff; 7 | pub use restart_policy::{RestartParams, RestartPolicy}; 8 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_unused_token.stderr: -------------------------------------------------------------------------------- 1 | error: the token must be used, or call `drop(_)` explicitly 2 | --> tests/ui/msg_unused_token.rs:8:23 3 | | 4 | 8 | (SomeRequest, _token) => {} 5 | | ^^^^^^ 6 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_double_wild.rs: -------------------------------------------------------------------------------- 1 | use elfo::{messages::Terminate, msg, Envelope}; 2 | 3 | fn test(envelope: Envelope) { 4 | msg!(match envelope { 5 | Terminate => {} 6 | a => {} 7 | b => {} 8 | }); 9 | } 10 | 11 | fn main() {} 12 | -------------------------------------------------------------------------------- /elfo-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Utils for unit testing actors. 2 | 3 | pub use proxy::{proxy, Proxy}; 4 | pub use utils::{extract_message, extract_request}; 5 | 6 | #[cfg(feature = "unstable")] 7 | pub use proxy::proxy_with_route; 8 | 9 | mod proxy; 10 | mod utils; 11 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_request_syntax_for_regular.rs: -------------------------------------------------------------------------------- 1 | use elfo::{message, msg, Envelope}; 2 | 3 | #[message] 4 | struct SomeEvent; 5 | 6 | fn test(envelope: Envelope) { 7 | msg!(match envelope { 8 | (SomeEvent, token) => {} 9 | }); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_unused_token.rs: -------------------------------------------------------------------------------- 1 | use elfo::{message, msg, Envelope}; 2 | 3 | #[message(ret = u32)] 4 | struct SomeRequest; 5 | 6 | fn test(envelope: Envelope) { 7 | msg!(match envelope { 8 | (SomeRequest, _token) => {} 9 | }); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_regular_syntax_for_request.rs: -------------------------------------------------------------------------------- 1 | use elfo::{message, msg, Envelope}; 2 | 3 | #[message(ret = u32)] 4 | struct SomeRequest; 5 | 6 | fn test(envelope: Envelope) { 7 | msg!(match envelope { 8 | SomeRequest => {} 9 | }); 10 | } 11 | 12 | fn main() {} 13 | -------------------------------------------------------------------------------- /elfo-macros-impl/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An internal crate for the `message` and `msg` macros. 2 | //! Prefer the `elfo-macros` crate if you don't need to wrap macros. 3 | 4 | extern crate proc_macro; 5 | 6 | mod errors; 7 | mod message; 8 | mod msg; 9 | 10 | pub use message::message_impl; 11 | pub use msg::msg_impl; 12 | -------------------------------------------------------------------------------- /examples/network/main.rs: -------------------------------------------------------------------------------- 1 | mod alice; 2 | mod bob; 3 | mod protocol; 4 | 5 | #[tokio::main] 6 | async fn main() { 7 | let topology = match std::env::args().nth(1).unwrap().as_str() { 8 | "alice" => alice::topology(), 9 | "bob" => bob::topology(), 10 | _ => unreachable!(), 11 | }; 12 | 13 | elfo::init::start(topology).await; 14 | } 15 | -------------------------------------------------------------------------------- /elfo-core/src/logging/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | 3 | mod control; 4 | 5 | // TODO: use `stability` instead. 6 | #[doc(hidden)] 7 | pub mod _priv { 8 | #[cfg(feature = "unstable")] // TODO: patch `stability` 9 | pub use super::control::{CheckResult, LoggingControl}; 10 | #[cfg(not(feature = "unstable"))] 11 | pub(crate) use super::control::{CheckResult, LoggingControl}; 12 | } 13 | -------------------------------------------------------------------------------- /elfo/tests/common.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] // TODO: combine tests into "it/*" 2 | #![allow(missing_docs)] 3 | 4 | // For tests without `elfo::test::proxy`. 5 | pub(crate) fn setup_logger() { 6 | let _ = tracing_subscriber::fmt() 7 | .with_target(false) 8 | .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) 9 | .with_test_writer() 10 | .try_init(); 11 | } 12 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_invalid_pattern.rs: -------------------------------------------------------------------------------- 1 | use elfo::{msg, Envelope}; 2 | 3 | // TODO: check more invalid patterns. 4 | fn test(envelope: Envelope) { 5 | msg!(match envelope { 6 | foo!() => {} 7 | "liternal" => {} 8 | 10..20 => {} 9 | (A | B) => {} 10 | (SomeRequest, token, extra) => {} 11 | (SomeRequest, 20) => {} 12 | }); 13 | } 14 | 15 | fn main() {} 16 | -------------------------------------------------------------------------------- /examples/network/bob.toml: -------------------------------------------------------------------------------- 1 | [system.telemeters] 2 | sink = "OpenMetrics" 3 | address = "0.0.0.0:9101" 4 | 5 | [system.dumpers] 6 | path = "example.bob.{class}.dump" 7 | 8 | [system.network] 9 | # TCP 10 | listen = ["tcp://127.0.0.1:9201"] 11 | discovery.predefined = ["tcp://127.0.0.1:9200"] 12 | 13 | # UDS (unix only) 14 | #listen = ["uds://example-bob.socket"] 15 | #discovery.predefined = ["uds://example-alice.socket"] 16 | -------------------------------------------------------------------------------- /examples/network/alice.toml: -------------------------------------------------------------------------------- 1 | [system.telemeters] 2 | sink = "OpenMetrics" 3 | address = "0.0.0.0:9100" 4 | 5 | [system.dumpers] 6 | path = "example.alice.{class}.dump" 7 | 8 | [system.network] 9 | # TCP 10 | listen = ["tcp://127.0.0.1:9200"] 11 | discovery.predefined = ["tcp://localhost:9201"] 12 | 13 | # UDS (unix only) 14 | #listen = ["uds://example-alice.socket"] 15 | #discovery.predefined = ["uds://example-bob.socket"] 16 | -------------------------------------------------------------------------------- /elfo-telemeter/src/metrics/counter.rs: -------------------------------------------------------------------------------- 1 | use super::MetricKind; 2 | 3 | pub(crate) struct Counter(u64); 4 | 5 | impl MetricKind for Counter { 6 | type Output = u64; 7 | type Shared = (); 8 | type Value = u64; 9 | 10 | fn new(_: Self::Shared) -> Self { 11 | Self(0) 12 | } 13 | 14 | fn update(&mut self, value: Self::Value) { 15 | self.0 += value; 16 | } 17 | 18 | fn merge(self, out: &mut Self::Output) -> usize { 19 | *out += self.0; 20 | 0 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /elfo-telemeter/src/metrics/mod.rs: -------------------------------------------------------------------------------- 1 | mod counter; 2 | mod gauge; 3 | mod histogram; 4 | 5 | pub(crate) use self::{ 6 | counter::Counter, 7 | gauge::{Gauge, GaugeOrigin}, 8 | histogram::Histogram, 9 | }; 10 | 11 | pub(crate) trait MetricKind: Sized { 12 | type Output; 13 | type Shared; 14 | type Value; 15 | 16 | fn new(shared: Self::Shared) -> Self; 17 | fn update(&mut self, value: Self::Value); 18 | 19 | /// Returns additional size to add to metrics. 20 | fn merge(self, out: &mut Self::Output) -> usize; 21 | } 22 | -------------------------------------------------------------------------------- /elfo-core/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use tokio::time::Instant; 4 | 5 | pub use self::{delay::Delay, interval::Interval}; 6 | 7 | mod delay; 8 | mod interval; 9 | 10 | fn far_future() -> Instant { 11 | // Copied from `tokio`. 12 | // Roughly 30 years from now. 13 | // API does not provide a way to obtain max `Instant` 14 | // or convert specific date in the future to instant. 15 | // 1000 years overflows on macOS, 100 years overflows on FreeBSD. 16 | Instant::now() + Duration::from_secs(86400 * 365 * 30) 17 | } 18 | -------------------------------------------------------------------------------- /elfo-macros-impl/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-macros-impl" 3 | version = "0.2.0-alpha.20" 4 | description = "Macros for matching and deriving messages, implementation internals" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "macros"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [dependencies] 18 | proc-macro2 = "1.0.75" 19 | quote = "1.0.35" 20 | syn = { version = "2.0.52", features = ["full", "extra-traits"] } 21 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_regular_syntax_for_request.stderr: -------------------------------------------------------------------------------- 1 | error[E0283]: type annotations needed 2 | --> tests/ui/msg_regular_syntax_for_request.rs:8:9 3 | | 4 | 8 | SomeRequest => {} 5 | | ^^^^^^^^^^^ cannot infer type 6 | | 7 | note: multiple `impl`s satisfying `SomeRequest: MustBeRegularNotRequest<_, Envelope>` found 8 | --> tests/ui/msg_regular_syntax_for_request.rs:7:5 9 | | 10 | 7 | / msg!(match envelope { 11 | 8 | | SomeRequest => {} 12 | 9 | | }); 13 | | |______^ 14 | = note: this error originates in the macro `msg` (in Nightly builds, run with -Z macro-backtrace for more info) 15 | -------------------------------------------------------------------------------- /elfo-utils/src/likely.rs: -------------------------------------------------------------------------------- 1 | //! `likely()` and `unlikely()` hints. 2 | //! `core::intrinsics::{likely, unlikely}` are unstable fo now. 3 | //! On stable we can use `#[cold]` to get the same effect. 4 | 5 | #[inline] 6 | #[cold] 7 | fn cold() {} 8 | 9 | /// Hints the compiler that this branch is likely to be taken. 10 | #[inline(always)] 11 | pub fn likely(b: bool) -> bool { 12 | if !b { 13 | cold(); 14 | } 15 | b 16 | } 17 | 18 | /// Hints the compiler that this branch is unlikely to be taken. 19 | #[inline(always)] 20 | pub fn unlikely(b: bool) -> bool { 21 | if b { 22 | cold(); 23 | } 24 | b 25 | } 26 | -------------------------------------------------------------------------------- /elfo-utils/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-utils" 3 | version = "0.2.7" 4 | description = "Some utils for the elfo system" 5 | keywords = ["elfo", "actor", "distributed"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [[bench]] 18 | name = "rate_limiter" 19 | harness = false 20 | 21 | [features] 22 | test-util = [] 23 | 24 | [dependencies] 25 | quanta = "0.12" 26 | crossbeam-utils = "0.8" 27 | 28 | [dev-dependencies] 29 | criterion.workspace = true 30 | -------------------------------------------------------------------------------- /elfo-macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-macros" 3 | version = "0.2.0-alpha.20" 4 | description = "Macros for matching and deriving messages" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "macros"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [lib] 18 | proc-macro = true 19 | 20 | [dependencies] 21 | elfo-macros-impl = { version = "=0.2.0-alpha.20", path = "../elfo-macros-impl" } 22 | 23 | syn = { version = "2.0.52", features = ["parsing", "printing"] } 24 | -------------------------------------------------------------------------------- /elfo-core/src/dumping/recorder.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, OnceLock}; 2 | 3 | use super::Dump; 4 | 5 | static MAKE_RECORDER: OnceLock = OnceLock::new(); 6 | 7 | type MakeRecorder = Box Arc + Sync + Send>; 8 | 9 | #[stability::unstable] 10 | pub trait Recorder: Send + Sync { 11 | fn enabled(&self) -> bool; 12 | fn record(&self, dump: Dump); 13 | } 14 | 15 | #[stability::unstable] 16 | pub fn set_make_recorder(make_recorder: MakeRecorder) -> bool { 17 | MAKE_RECORDER.set(make_recorder).is_ok() 18 | } 19 | 20 | pub(crate) fn make_recorder(class: &'static str) -> Option> { 21 | MAKE_RECORDER.get().map(|make| make(class)) 22 | } 23 | -------------------------------------------------------------------------------- /benches/trace_id.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | 5 | use elfo::{tracing::TraceId, Context}; 6 | 7 | mod common; 8 | 9 | fn generate(c: &mut Criterion) { 10 | async fn testee(_ctx: Context, iter_count: u64) -> Duration { 11 | let start = Instant::now(); 12 | for _ in 0..iter_count { 13 | black_box(TraceId::generate()); 14 | } 15 | start.elapsed() 16 | } 17 | 18 | c.bench_function("generate", |b| { 19 | b.iter_custom(|iter_count| common::bench_singleton(iter_count, testee)) 20 | }); 21 | } 22 | 23 | criterion_group!(cases, generate); 24 | criterion_main!(cases); 25 | -------------------------------------------------------------------------------- /elfo-pinger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-pinger" 3 | version = "0.2.0-alpha.20" 4 | description = "Pings groups of the elfo system" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "pinging"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [dependencies] 18 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable"] } 19 | elfo-utils = { version = "0.2.7", path = "../elfo-utils" } 20 | 21 | tokio = { workspace = true, features = ["time"] } 22 | serde.workspace = true 23 | tracing.workspace = true 24 | humantime-serde = "1" 25 | -------------------------------------------------------------------------------- /elfo-network/src/discovery/advise_timer.rs: -------------------------------------------------------------------------------- 1 | use tokio::time::Instant; 2 | 3 | /// Helper type which suggests when new timer should be 4 | /// created. 5 | pub(crate) struct AdviseTimer { 6 | readvise_at: Instant, 7 | } 8 | 9 | pub(crate) enum NewTimerSetup { 10 | Do { at: Instant }, 11 | OldIsStillTicking, 12 | } 13 | 14 | impl AdviseTimer { 15 | pub(crate) fn new() -> Self { 16 | Self { 17 | readvise_at: Instant::now(), 18 | } 19 | } 20 | 21 | pub(crate) fn feed(&mut self, advise: Instant) -> NewTimerSetup { 22 | if advise >= self.readvise_at { 23 | self.readvise_at = advise; 24 | NewTimerSetup::Do { at: advise } 25 | } else { 26 | NewTimerSetup::OldIsStillTicking 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /elfo-core/src/tracing/mod.rs: -------------------------------------------------------------------------------- 1 | //! Includes `TraceId` and useful utilities around it. 2 | //! For more details see [The Actoromicon](https://actoromicon.rs/ch05-04-tracing.html). 3 | 4 | use std::cell::RefCell; 5 | 6 | use self::generator::{ChunkRegistry, Generator}; 7 | 8 | pub use self::{trace_id::TraceId, validator::TraceIdValidator}; 9 | 10 | impl TraceId { 11 | /// Generates a new trace id according to [the schema](https://actoromicon.rs/ch05-04-tracing.html#traceid). 12 | pub fn generate() -> Self { 13 | GENERATOR.with(|cell| cell.borrow_mut().generate(&CHUNK_REGISTRY)) 14 | } 15 | } 16 | 17 | static CHUNK_REGISTRY: ChunkRegistry = ChunkRegistry::new(0); 18 | thread_local! { 19 | static GENERATOR: RefCell = RefCell::new(Generator::default()); 20 | } 21 | 22 | mod generator; 23 | mod trace_id; 24 | mod validator; 25 | -------------------------------------------------------------------------------- /elfo-core/src/thread.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | hash::{Hash, Hasher}, 3 | thread, 4 | }; 5 | 6 | pub(crate) type ThreadId = u64; 7 | 8 | pub(crate) fn id() -> ThreadId { 9 | // TODO: just use `ThreadId::as_u64()` after stabilization. 10 | // See https://github.com/rust-lang/rust/issues/67939. 11 | struct RawIdExtractor(u64); 12 | 13 | impl Hasher for RawIdExtractor { 14 | fn write(&mut self, _bytes: &[u8]) { 15 | panic!("cannot extract thread ID"); 16 | } 17 | 18 | fn write_u64(&mut self, i: u64) { 19 | self.0 = i; 20 | } 21 | 22 | fn finish(&self) -> u64 { 23 | self.0 24 | } 25 | } 26 | 27 | let opaque_id = thread::current().id(); 28 | let mut extractor = RawIdExtractor(0); 29 | opaque_id.hash(&mut extractor); 30 | extractor.finish() 31 | } 32 | -------------------------------------------------------------------------------- /elfo-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-test" 3 | version = "0.2.0-alpha.20" 4 | description = "Test utils for the elfo system" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "testing"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [features] 18 | unstable = [] 19 | 20 | [dependencies] 21 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core" } 22 | elfo-configurer = { version = "0.2.0-alpha.20", path = "../elfo-configurer" } 23 | 24 | tokio.workspace = true 25 | stability.workspace = true 26 | tracing-subscriber = { workspace = true, features = ["env-filter"] } 27 | serde.workspace = true 28 | serde-value = "0.7.0" 29 | futures-intrusive = "0.5" 30 | -------------------------------------------------------------------------------- /elfo-macros-impl/src/errors.rs: -------------------------------------------------------------------------------- 1 | use std::cell::RefCell; 2 | 3 | use proc_macro2::TokenStream; 4 | use syn::Error; 5 | 6 | thread_local! { 7 | static ERROR: RefCell> = const { RefCell::new(None) }; 8 | } 9 | 10 | macro_rules! emit_error { 11 | ($span:expr, $($tt:tt)*) => { 12 | crate::errors::emit(syn::Error::new($span, format!($($tt)*))) 13 | } 14 | } 15 | 16 | pub(crate) use emit_error; 17 | 18 | pub(crate) fn emit(error: Error) { 19 | ERROR.with(|combined| { 20 | let mut combined = combined.borrow_mut(); 21 | 22 | if let Some(combined) = &mut *combined { 23 | combined.combine(error); 24 | } else { 25 | *combined = Some(error); 26 | } 27 | }); 28 | } 29 | 30 | pub(crate) fn into_tokens() -> Option { 31 | ERROR.with(|e| e.borrow_mut().take().map(|e| e.into_compile_error())) 32 | } 33 | -------------------------------------------------------------------------------- /elfo-core/src/panic.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | any::Any, 3 | future::Future, 4 | panic::{self, AssertUnwindSafe}, 5 | }; 6 | 7 | use futures::FutureExt; 8 | 9 | pub(crate) fn sync_catch(f: impl FnOnce() -> R) -> Result { 10 | panic::catch_unwind(AssertUnwindSafe(f)).map_err(panic_to_string) 11 | } 12 | 13 | pub(crate) async fn catch(f: impl Future) -> Result { 14 | AssertUnwindSafe(f) 15 | .catch_unwind() 16 | .await 17 | .map_err(panic_to_string) 18 | } 19 | 20 | fn panic_to_string(payload: Box) -> String { 21 | if let Some(message) = payload.downcast_ref::<&str>() { 22 | format!("panic: {message}") 23 | } else if let Some(message) = payload.downcast_ref::() { 24 | format!("panic: {message}") 25 | } else { 26 | "panic: ".to_string() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /elfo-dumper/src/recorder.rs: -------------------------------------------------------------------------------- 1 | use elfo_core::{ 2 | dumping::{CheckResult, Dump, Recorder}, 3 | scope, 4 | }; 5 | 6 | use crate::dump_storage::DumpRegistry; 7 | 8 | impl Recorder for DumpRegistry { 9 | fn enabled(&self) -> bool { 10 | scope::try_with(|scope| match scope.dumping().check(self.class()) { 11 | CheckResult::Passed => { 12 | // TODO: `elfo_lost_dumps_total` 13 | // TODO: `elfo_emitted_dumps_total` 14 | true 15 | } 16 | CheckResult::NotInterested => false, 17 | CheckResult::Limited => { 18 | // TODO: `elfo_lost_dumps_total` 19 | false 20 | } 21 | }) 22 | // TODO: limit dumps outside the actor system? 23 | .unwrap_or(false) 24 | } 25 | 26 | fn record(&self, dump: Dump) { 27 | self.add(dump); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /elfo-configurer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-configurer" 3 | version = "0.2.0-alpha.20" 4 | description = "Loads and distributes configs across the elfo system" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "configuration"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [dependencies] 18 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable"] } 19 | 20 | toml.workspace = true 21 | tokio = { workspace = true, features = ["fs"] } 22 | futures.workspace = true 23 | serde = { workspace = true, features = ["rc"] } 24 | tracing.workspace = true 25 | serde-value = "0.7.0" 26 | fxhash = "0.2.1" 27 | 28 | [dev-dependencies] 29 | serde_json = "1.0.94" 30 | 31 | [features] 32 | test-util = [] 33 | -------------------------------------------------------------------------------- /elfo-core/src/dumping/config.rs: -------------------------------------------------------------------------------- 1 | //! [Config]. 2 | //! 3 | //! [Config]: DumpingConfig 4 | 5 | use serde::Deserialize; 6 | 7 | /// Dumping configuration. 8 | /// 9 | /// # Example 10 | /// ```toml 11 | /// [some_group] 12 | /// system.dumping.disabled = false 13 | /// system.dumping.max_rate = 1_000 14 | /// ``` 15 | #[derive(Debug, Clone, Deserialize)] 16 | #[serde(default)] 17 | pub struct DumpingConfig { 18 | /// Whether dumping is disabled. 19 | /// 20 | /// `false` by default. 21 | pub disabled: bool, 22 | /// Maximum rate of dumping. 23 | /// Exceeding this rate will cause messages to not be dumped. 24 | /// 25 | /// `100_000` by default. 26 | pub max_rate: u64, 27 | // TODO: per class overrides. 28 | } 29 | 30 | impl Default for DumpingConfig { 31 | fn default() -> Self { 32 | Self { 33 | disabled: false, 34 | max_rate: 100_000, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /elfo-core/src/supervisor/error_chain.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, fmt}; 2 | 3 | pub(crate) struct ErrorChain<'a>(pub(crate) &'a dyn Error); 4 | 5 | impl fmt::Display for ErrorChain<'_> { 6 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 7 | write!(f, "{}", self.0)?; 8 | 9 | let mut cursor = self.0; 10 | while let Some(err) = cursor.source() { 11 | write!(f, ": {err}")?; 12 | cursor = err; 13 | } 14 | 15 | Ok(()) 16 | } 17 | } 18 | 19 | #[test] 20 | fn trivial_error_chain() { 21 | let error = anyhow::anyhow!("oops"); 22 | assert_eq!(format!("{}", ErrorChain(&*error)), "oops"); 23 | } 24 | 25 | #[test] 26 | fn error_chain() { 27 | let innermost = anyhow::anyhow!("innermost"); 28 | let inner = innermost.context("inner"); 29 | let outer = inner.context("outer"); 30 | assert_eq!( 31 | format!("{}", ErrorChain(&*outer)), 32 | "outer: inner: innermost" 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /elfo-network/src/rtt.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use metrics::gauge; 4 | 5 | pub(crate) struct Rtt { 6 | ema: Option, 7 | alpha: f64, 8 | } 9 | 10 | impl Rtt { 11 | pub(crate) fn new(samples: usize) -> Self { 12 | // https://en.wikipedia.org/wiki/Moving_average#Relationship_between_SMA_and_EMA 13 | let alpha = 2.0 / (samples + 1) as f64; 14 | 15 | Self { ema: None, alpha } 16 | } 17 | 18 | pub(crate) fn push(&mut self, rtt: Duration) { 19 | let rtt = rtt.as_secs_f64(); 20 | 21 | let ema = if let Some(ema) = self.ema { 22 | ema * (1.0 - self.alpha) + rtt * self.alpha 23 | } else { 24 | rtt 25 | }; 26 | 27 | gauge!("elfo_network_rtt_seconds", ema); 28 | 29 | self.ema = Some(ema); 30 | } 31 | } 32 | 33 | impl Drop for Rtt { 34 | fn drop(&mut self) { 35 | if self.ema.is_some() { 36 | gauge!("elfo_network_rtt_seconds", f64::NAN); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_invalid_pattern.stderr: -------------------------------------------------------------------------------- 1 | error: macros in pattern position are forbidden 2 | --> tests/ui/msg_invalid_pattern.rs:6:9 3 | | 4 | 6 | foo!() => {} 5 | | ^^^ 6 | 7 | error: literal patterns are forbidden 8 | --> tests/ui/msg_invalid_pattern.rs:7:9 9 | | 10 | 7 | "liternal" => {} 11 | | ^^^^^^^^^^ 12 | 13 | error: range patterns are forbidden 14 | --> tests/ui/msg_invalid_pattern.rs:8:9 15 | | 16 | 8 | 10..20 => {} 17 | | ^^ 18 | 19 | error: parenthesized patterns are forbidden 20 | --> tests/ui/msg_invalid_pattern.rs:9:9 21 | | 22 | 9 | (A | B) => {} 23 | | ^^^^^^^ 24 | 25 | error: invalid request pattern 26 | --> tests/ui/msg_invalid_pattern.rs:10:9 27 | | 28 | 10 | (SomeRequest, token, extra) => {} 29 | | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 30 | 31 | error: token must be identifier 32 | --> tests/ui/msg_invalid_pattern.rs:11:9 33 | | 34 | 11 | (SomeRequest, 20) => {} 35 | | ^^^^^^^^^^^^^^^^^ 36 | -------------------------------------------------------------------------------- /elfo-logger/src/stats.rs: -------------------------------------------------------------------------------- 1 | use metrics::{Key, Label}; 2 | use tracing::Level; 3 | 4 | fn labels_by_level(level: Level) -> &'static [Label] { 5 | const fn f(value: &'static str) -> Label { 6 | Label::from_static_parts("level", value) 7 | } 8 | 9 | const TRACE_LABELS: &[Label] = &[f("Trace")]; 10 | const DEBUG_LABELS: &[Label] = &[f("Debug")]; 11 | const INFO_LABELS: &[Label] = &[f("Info")]; 12 | const WARN_LABELS: &[Label] = &[f("Warn")]; 13 | const ERROR_LABELS: &[Label] = &[f("Error")]; 14 | 15 | match level { 16 | Level::TRACE => TRACE_LABELS, 17 | Level::DEBUG => DEBUG_LABELS, 18 | Level::INFO => INFO_LABELS, 19 | Level::WARN => WARN_LABELS, 20 | Level::ERROR => ERROR_LABELS, 21 | } 22 | } 23 | 24 | pub(crate) fn counter_per_level(name: &'static str, level: Level) { 25 | let recorder = ward!(metrics::try_recorder()); 26 | let labels = labels_by_level(level); 27 | let key = Key::from_static_parts(name, labels); 28 | recorder.increment_counter(&key, 1); 29 | } 30 | -------------------------------------------------------------------------------- /elfo/tests/ui/msg_request_syntax_for_regular.stderr: -------------------------------------------------------------------------------- 1 | error[E0277]: the trait bound `SomeEvent: elfo::Request` is not satisfied 2 | --> tests/ui/msg_request_syntax_for_regular.rs:8:10 3 | | 4 | 8 | (SomeEvent, token) => {} 5 | | ^^^^^^^^^ unsatisfied trait bound 6 | | 7 | help: the trait `elfo::Request` is not implemented for `SomeEvent` 8 | --> tests/ui/msg_request_syntax_for_regular.rs:4:1 9 | | 10 | 4 | struct SomeEvent; 11 | | ^^^^^^^^^^^^^^^^ 12 | = help: the following other types implement trait `elfo::Request`: 13 | Ping 14 | ReloadConfigs 15 | StartEntrypoint 16 | UpdateConfig 17 | ValidateConfig 18 | note: required by a bound in `must_be_request` 19 | --> tests/ui/msg_request_syntax_for_regular.rs:7:5 20 | | 21 | 7 | / msg!(match envelope { 22 | 8 | | (SomeEvent, token) => {} 23 | 9 | | }); 24 | | |______^ required by this bound in `must_be_request` 25 | = note: this error originates in the macro `msg` (in Nightly builds, run with -Z macro-backtrace for more info) 26 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # elfo examples 2 | 3 | This directory contains a collection of examples that demonstrate the use of the `elfo` ecosystem. 4 | 5 | Run `cargo run --bin ` to execute specific example if not specified something other. 6 | 7 | ## usage 8 | 9 | Describes common concepts of `elfo`, also how to work with configuration, combine actors together, enable metrics, logs and dumps. 10 | 11 | ## network 12 | 13 | Shows how to connect distributed actor groups. 14 | 15 | For simplicity, it uses one common binary that runs a specific service based on the CLI argument: 16 | ```sh 17 | cargo run --bin network --features network -- alice & 18 | cargo run --bin network --features network -- bob 19 | ``` 20 | 21 | ## stream 22 | 23 | Demonstrates how to attach streams to an actor's context. 24 | 25 | ## test 26 | 27 | Shows how to write functional tests for your actors. 28 | 29 | Run it as `cargo test --bin test --features test-util`. 30 | 31 | ## tokio-broadcast 32 | 33 | Demonstrates how to attach other channels to an actor's context, e.g. `tokio::sync::broadcast`. 34 | -------------------------------------------------------------------------------- /elfo-pinger/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Periodically pings all actors in the topology to check if they are alive. 2 | //! [Configuration]. 3 | //! 4 | //! [Configuration]: config::Config 5 | 6 | use std::time::Duration; 7 | 8 | use elfo_core::{ActorGroup, Blueprint, RestartParams, RestartPolicy, Topology}; 9 | 10 | pub mod config; 11 | 12 | mod actor; 13 | 14 | /// Creates a blueprint. 15 | /// 16 | /// # Example 17 | /// ``` 18 | /// # use elfo_core as elfo; 19 | /// let topology = elfo::Topology::empty(); 20 | /// let pingers = topology.local("pingers"); 21 | /// 22 | /// // Usually, it's `elfo::batteries::pinger::fixture`. 23 | /// pingers.mount(elfo_pinger::new(&topology)); 24 | /// ``` 25 | pub fn new(topology: &Topology) -> Blueprint { 26 | let topology = topology.clone(); 27 | ActorGroup::new() 28 | .config::() 29 | .restart_policy(RestartPolicy::on_failure(RestartParams::new( 30 | Duration::from_secs(5), 31 | Duration::from_secs(30), 32 | ))) 33 | .stop_order(100) 34 | .exec(move |ctx| actor::exec(ctx, topology.clone())) 35 | } 36 | -------------------------------------------------------------------------------- /examples/stream.rs: -------------------------------------------------------------------------------- 1 | use elfo::{config::AnyConfig, prelude::*, stream::Stream}; 2 | use futures::stream; 3 | 4 | #[message] 5 | struct SomeMessage(u32); 6 | 7 | #[message] 8 | struct EndOfMessages; 9 | 10 | fn samples() -> Blueprint { 11 | ActorGroup::new().exec(|mut ctx| async move { 12 | let stream = Stream::from_futures03(stream::iter(vec![SomeMessage(0), SomeMessage(1)])); 13 | ctx.attach(stream); 14 | 15 | while let Some(_envelope) = ctx.recv().await { 16 | // ... 17 | } 18 | }) 19 | } 20 | 21 | #[tokio::main] 22 | async fn main() { 23 | let topology = elfo::Topology::empty(); 24 | let logger = elfo::batteries::logger::init(); 25 | 26 | let samples = topology.local("samples"); 27 | let loggers = topology.local("loggers"); 28 | let configurers = topology.local("system.configurers").entrypoint(); 29 | 30 | samples.mount(self::samples()); 31 | loggers.mount(logger); 32 | configurers.mount(elfo::batteries::configurer::fixture( 33 | &topology, 34 | AnyConfig::default(), 35 | )); 36 | 37 | elfo::init::start(topology).await; 38 | } 39 | -------------------------------------------------------------------------------- /elfo-pinger/src/config.rs: -------------------------------------------------------------------------------- 1 | //! Configuration for the pinger. 2 | //! 3 | //! Note: all types here are exported only for documentation purposes 4 | //! and are not subject to stable guarantees. However, the config 5 | //! structure (usually encoded in TOML) follows stable guarantees. 6 | 7 | use std::time::Duration; 8 | 9 | use serde::Deserialize; 10 | 11 | /// The pinger's config. 12 | /// 13 | /// # Example 14 | /// ```toml 15 | /// [system.pingers] 16 | /// ping_interval = "30s" 17 | /// ``` 18 | #[derive(Debug, Deserialize)] 19 | pub struct Config { 20 | /// How often pingers should ping all other actors. 21 | /// 22 | /// `10s` by default. 23 | #[serde(with = "humantime_serde", default = "default_ping_interval")] 24 | pub ping_interval: Duration, 25 | /// How long to wait for a response before logging a warning. 26 | /// 27 | /// `5s` by default. 28 | #[serde(with = "humantime_serde", default = "default_warn_threshold")] 29 | pub warn_threshold: Duration, 30 | } 31 | 32 | fn default_ping_interval() -> Duration { 33 | Duration::from_secs(10) 34 | } 35 | 36 | fn default_warn_threshold() -> Duration { 37 | Duration::from_secs(5) 38 | } 39 | -------------------------------------------------------------------------------- /elfo-core/src/demux.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use smallvec::SmallVec; 4 | 5 | use crate::{envelope::Envelope, Addr}; 6 | 7 | const OPTIMAL_COUNT: usize = 5; 8 | type Addrs = SmallVec<[Addr; OPTIMAL_COUNT]>; 9 | 10 | // Actually, it's a private type, `pub` is for `Destination` only. 11 | #[derive(Default, Clone)] 12 | pub struct Demux { 13 | #[allow(clippy::type_complexity)] 14 | filter: Option>, 15 | } 16 | 17 | impl Demux { 18 | pub(crate) fn append(&mut self, f: impl Fn(&Envelope, &mut Addrs) + Send + Sync + 'static) { 19 | self.filter = Some(if let Some(prev) = self.filter.take() { 20 | Arc::new(move |envelope, addrs| { 21 | prev(envelope, addrs); 22 | f(envelope, addrs); 23 | }) 24 | } else { 25 | Arc::new(f) 26 | }) 27 | } 28 | 29 | // TODO: return an iterator? 30 | pub(crate) fn filter(&self, envelope: &Envelope) -> Addrs { 31 | let mut addrs = Addrs::new(); 32 | if let Some(filter) = &self.filter { 33 | (filter)(envelope, &mut addrs); 34 | } 35 | addrs 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /elfo-utils/src/time/mod.rs: -------------------------------------------------------------------------------- 1 | //! Provides [`Instant`] and [`SystemTime`] types. 2 | //! 3 | //! The main purpose is to provide a way to mock system/monotonic time in tests. 4 | 5 | mod instant; 6 | mod system; 7 | 8 | pub use self::{instant::*, system::*}; 9 | 10 | #[cfg(any(test, feature = "test-util"))] 11 | pub use mock::{with_mock, TimeMock}; 12 | 13 | #[cfg(any(test, feature = "test-util"))] 14 | mod mock { 15 | use std::time::Duration; 16 | 17 | use super::*; 18 | 19 | /// Mocks `SystemTime` and `Instant`, see [`TimeMock`]. 20 | pub fn with_mock(f: impl FnOnce(TimeMock)) { 21 | with_system_time_mock(|system| { 22 | with_instant_mock(|instant| { 23 | f(TimeMock { system, instant }); 24 | }); 25 | }); 26 | } 27 | 28 | /// Controllable time source for use in tests. 29 | pub struct TimeMock { 30 | system: SystemTimeMock, 31 | instant: InstantMock, 32 | } 33 | 34 | impl TimeMock { 35 | /// Increase the time by the given duration. 36 | pub fn advance(&self, duration: Duration) { 37 | self.system.advance(duration); 38 | self.instant.advance(duration); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /elfo-dumper/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-dumper" 3 | version = "0.2.0-alpha.20" 4 | description = "Dumps messages of the elfo system on disk" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "dumping"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [dependencies] 18 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable"] } 19 | elfo-utils = { version = "0.2.7", path = "../elfo-utils" } 20 | 21 | metrics.workspace = true 22 | bytesize.workspace = true 23 | eyre.workspace = true 24 | tokio = { workspace = true, features = ["fs", "io-util", "sync"] } 25 | serde.workspace = true 26 | libc.workspace = true 27 | thread_local.workspace = true 28 | parking_lot.workspace = true 29 | tracing.workspace = true 30 | fxhash = "0.2.1" 31 | humantime-serde = "1" 32 | serde_json = "1.0.64" 33 | 34 | [dev-dependencies] 35 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["test-util"] } 36 | elfo-test = { path = "../elfo-test" } 37 | 38 | toml.workspace = true 39 | tempdir = "0.3.7" 40 | -------------------------------------------------------------------------------- /elfo-dumper/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Writes dumps of messages to files. [Configuration]. 2 | //! 3 | //! Each line is a valid JSON. Lines can be unordered. 4 | //! 5 | //! For more details about dumping see [The Actoromicon]. 6 | //! 7 | //! [Configuration]: crate::config::Config 8 | //! [The Actoromicon]: https://actoromicon.rs/ch05-03-dumping.html 9 | 10 | use std::sync::Arc; 11 | 12 | use parking_lot::Mutex; 13 | use tracing::error; 14 | 15 | use elfo_core::{ 16 | dumping::{self, Recorder}, 17 | Blueprint, 18 | }; 19 | 20 | use self::dump_storage::DumpStorage; 21 | 22 | mod actor; 23 | mod dump_storage; 24 | mod file_registry; 25 | mod recorder; 26 | mod reporter; 27 | mod rule_set; 28 | mod serializer; 29 | 30 | pub mod config; 31 | 32 | /// Installs a global dump recorder and returns a group to handle dumps. 33 | pub fn new() -> Blueprint { 34 | let storage = Arc::new(Mutex::new(DumpStorage::new())); 35 | let blueprint = actor::new(storage.clone()); 36 | 37 | let is_ok = dumping::set_make_recorder(Box::new(move |class| { 38 | storage.lock().registry(class) as Arc 39 | })); 40 | 41 | if !is_ok { 42 | error!("failed to set a dump recorder"); 43 | } 44 | 45 | blueprint 46 | } 47 | -------------------------------------------------------------------------------- /elfo-core/src/dumping/dumper.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::Message; 4 | 5 | use super::{ 6 | dump::*, 7 | recorder::{self, Recorder}, 8 | }; 9 | 10 | #[derive(Clone)] 11 | #[stability::unstable] 12 | pub struct Dumper { 13 | recorder: Option>, 14 | } 15 | 16 | impl Dumper { 17 | pub fn new(class: &'static str) -> Self { 18 | Self { 19 | recorder: recorder::make_recorder(class), 20 | } 21 | } 22 | 23 | #[inline] 24 | #[stability::unstable] 25 | pub fn acquire(&self) -> Option> { 26 | let r = self.recorder.as_deref().filter(|r| r.enabled())?; 27 | Some(DumpingPermit { recorder: r }) 28 | } 29 | 30 | pub(crate) fn acquire_m(&self, message: &M) -> Option> { 31 | if !message.dumping_allowed() { 32 | return None; 33 | } 34 | 35 | self.acquire() 36 | } 37 | } 38 | 39 | #[must_use] 40 | #[stability::unstable] 41 | pub struct DumpingPermit<'a> { 42 | recorder: &'a dyn Recorder, 43 | } 44 | 45 | impl DumpingPermit<'_> { 46 | #[stability::unstable] 47 | pub fn record(self, dump: Dump) { 48 | self.recorder.record(dump); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-examples" 3 | version = "0.0.0" 4 | publish = false 5 | 6 | repository.workspace = true 7 | authors.workspace = true 8 | license.workspace = true 9 | edition.workspace = true 10 | readme.workspace = true 11 | rust-version.workspace = true 12 | 13 | [dependencies] 14 | elfo = { path = "../elfo", features = ["full"] } 15 | elfo-telemeter = { path = "../elfo-telemeter" } # for `AllocatorStats` 16 | 17 | tokio = { workspace = true, features = ["full"] } 18 | metrics.workspace = true 19 | toml.workspace = true 20 | derive_more.workspace = true 21 | futures.workspace = true 22 | serde.workspace = true 23 | tracing.workspace = true 24 | anyhow = "1.0.40" 25 | humantime-serde = "1" 26 | 27 | [features] 28 | test-util = ["elfo/test-util"] 29 | network = ["elfo/network"] 30 | unstable = ["elfo/unstable"] 31 | 32 | [[bin]] 33 | name = "usage" 34 | path = "usage/main.rs" 35 | 36 | [[bin]] 37 | name = "stream" 38 | path = "stream.rs" 39 | 40 | [[bin]] 41 | name = "network" 42 | path = "network/main.rs" 43 | required-features = ["network"] 44 | 45 | [[bin]] 46 | name = "test" 47 | path = "test.rs" 48 | required-features = ["test-util"] 49 | 50 | [[bin]] 51 | name = "tokio-broadcast" 52 | path = "tokio-broadcast.rs" 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elfo 2 | 3 | [![Crates.io][crates-badge]][crates-url] 4 | [![Documentation][docs-badge]][docs-url] 5 | [![MIT licensed][mit-badge]][mit-url] 6 | [![Build Status][actions-badge]][actions-url] 7 | 8 | [crates-badge]: https://img.shields.io/crates/v/elfo.svg 9 | [crates-url]: https://crates.io/crates/elfo 10 | [docs-badge]: https://img.shields.io/docsrs/elfo 11 | [docs-url]: https://docs.rs/elfo/0.2.0-alpha.20/elfo 12 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 13 | [mit-url]: https://github.com/loyd/elfo/blob/master/LICENSE 14 | [actions-badge]: https://github.com/elfo-rs/elfo/actions/workflows/ci.yml/badge.svg 15 | [actions-url]: https://github.com/elfo-rs/elfo/actions/workflows/ci.yml 16 | 17 | Elfo is another actor system. Check [The Actoromicon](http://actoromicon.rs/). 18 | 19 | **Note: although it's already actively used in production, it's still under development. Wait for v0.2 for public announcement.** 20 | 21 | ## Usage 22 | To use `elfo`, add this to your `Cargo.toml`: 23 | ```toml 24 | [dependencies] 25 | elfo = { version = "0.2.0-alpha.20", features = ["full"] } 26 | 27 | [dev-dependencies] 28 | elfo = { version = "0.2.0-alpha.20", features = ["test-util"] } 29 | ``` 30 | 31 | ## Examples 32 | [Examples](examples). 33 | -------------------------------------------------------------------------------- /elfo-core/src/runtime.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use tokio::runtime::Handle; 4 | 5 | use crate::actor::ActorMeta; 6 | #[cfg(feature = "unstable-stuck-detection")] 7 | use crate::stuck_detection::StuckDetector; 8 | 9 | pub(crate) trait RuntimeFilter: Fn(&ActorMeta) -> bool + Send + Sync + 'static {} 10 | impl bool + Send + Sync + 'static> RuntimeFilter for F {} 11 | 12 | // === RuntimeManager === 13 | 14 | #[derive(Default, Clone)] 15 | pub(crate) struct RuntimeManager { 16 | dedicated: Vec<(Arc, Handle)>, 17 | #[cfg(feature = "unstable-stuck-detection")] 18 | stuck_detector: StuckDetector, 19 | } 20 | 21 | impl RuntimeManager { 22 | pub(crate) fn add(&mut self, filter: F, handle: Handle) { 23 | self.dedicated.push((Arc::new(filter), handle)); 24 | } 25 | 26 | pub(crate) fn get(&self, meta: &ActorMeta) -> Handle { 27 | for (f, h) in &self.dedicated { 28 | if f(meta) { 29 | return h.clone(); 30 | } 31 | } 32 | 33 | tokio::runtime::Handle::current() 34 | } 35 | 36 | #[cfg(feature = "unstable-stuck-detection")] 37 | pub(crate) fn stuck_detector(&self) -> StuckDetector { 38 | self.stuck_detector.clone() 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /elfo-core/src/logging/control.rs: -------------------------------------------------------------------------------- 1 | use tracing::{Level, Metadata}; 2 | 3 | use elfo_utils::{CachePadded, RateLimit, RateLimiter}; 4 | 5 | use super::config::LoggingConfig; 6 | 7 | #[derive(Default)] 8 | #[stability::unstable] 9 | pub struct LoggingControl { 10 | limiters: [CachePadded; 5], 11 | } 12 | 13 | impl LoggingControl { 14 | pub(crate) fn configure(&self, config: &LoggingConfig) { 15 | for limiter in &self.limiters { 16 | limiter.configure(RateLimit::Rps(config.max_rate_per_level)); 17 | } 18 | } 19 | 20 | pub fn check(&self, meta: &Metadata<'_>) -> CheckResult { 21 | let limiter = &self.limiters[log_level_to_value(*meta.level())]; 22 | if limiter.acquire() { 23 | CheckResult::Passed 24 | } else { 25 | CheckResult::Limited 26 | } 27 | } 28 | } 29 | 30 | pub enum CheckResult { 31 | Passed, 32 | NotInterested, 33 | Limited, 34 | } 35 | 36 | fn log_level_to_value(level: Level) -> usize { 37 | // The compiler should optimize this expression because `tracing::Level` is 38 | // based on the same values internally. 39 | match level { 40 | Level::TRACE => 0, 41 | Level::DEBUG => 1, 42 | Level::INFO => 2, 43 | Level::WARN => 3, 44 | Level::ERROR => 4, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /elfo-core/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! assert_msg { 3 | ($envelope:expr, $pat:pat) => {{ 4 | let envelope = &$envelope; 5 | let message = envelope.message(); 6 | 7 | // TODO: use `msg!` to support multiple messages in a pattern. 8 | #[allow(unreachable_patterns)] 9 | match &message.downcast_ref() { 10 | Some($pat) => {} 11 | _ => panic!( 12 | "\na message doesn't match a pattern\npattern: {}\nmessage: {message:#?}\n", 13 | stringify!($pat), 14 | ), 15 | } 16 | }}; 17 | } 18 | 19 | #[macro_export] 20 | macro_rules! assert_msg_eq { 21 | ($envelope:expr, $expected:expr) => {{ 22 | let envelope = &$envelope; 23 | 24 | let Some(actual) = envelope.message().downcast_ref() else { 25 | panic!("unexpected message: {:#?}", envelope.message()); 26 | }; 27 | let expected = &$expected; 28 | 29 | fn unify(_rhs: &T, _lhs: &T) {} 30 | unify(actual, expected); 31 | 32 | assert_eq!(actual, expected); 33 | }}; 34 | } 35 | 36 | macro_rules! cfg_network { 37 | // Force `{..}` to make rustfmt work. 38 | ({$($item:item)*}) => { 39 | $( 40 | #[cfg(feature = "network")] 41 | $item 42 | )* 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /elfo-core/src/dumping/sequence_no.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::TryFrom, 3 | num::{NonZeroU64, TryFromIntError}, 4 | sync::atomic::{AtomicU64, Ordering}, 5 | }; 6 | 7 | use serde::Serialize; 8 | 9 | // TODO: make it just type alias (or not?) 10 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize)] 11 | pub struct SequenceNo(NonZeroU64); 12 | 13 | impl TryFrom for SequenceNo { 14 | type Error = TryFromIntError; 15 | 16 | #[inline] 17 | fn try_from(raw: u64) -> Result { 18 | NonZeroU64::try_from(raw).map(Self) 19 | } 20 | } 21 | 22 | impl From for u64 { 23 | #[inline] 24 | fn from(sequence_no: SequenceNo) -> Self { 25 | sequence_no.0.get() 26 | } 27 | } 28 | 29 | pub(crate) struct SequenceNoGenerator { 30 | next_sequence_no: AtomicU64, 31 | } 32 | 33 | impl Default for SequenceNoGenerator { 34 | fn default() -> Self { 35 | Self { 36 | // We starts with `1` here because `SequenceNo` is supposed to be non-zero. 37 | next_sequence_no: AtomicU64::new(1), 38 | } 39 | } 40 | } 41 | 42 | impl SequenceNoGenerator { 43 | pub(crate) fn generate(&self) -> SequenceNo { 44 | let raw = self.next_sequence_no.fetch_add(1, Ordering::Relaxed); 45 | NonZeroU64::new(raw).map(SequenceNo).expect("impossible") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /elfo-macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Contains `msg!` and `message!` proc-macros. 2 | 3 | use proc_macro::TokenStream; 4 | use syn::parse_quote; 5 | 6 | use elfo_macros_impl::{message_impl, msg_impl}; 7 | 8 | /// Matches a message based on the provided envelope. 9 | #[proc_macro] 10 | pub fn msg(input: TokenStream) -> TokenStream { 11 | msg_impl(input, parse_quote!(::elfo)) 12 | } 13 | 14 | #[doc(hidden)] 15 | #[proc_macro] 16 | pub fn msg_core(input: TokenStream) -> TokenStream { 17 | msg_impl(input, parse_quote!(::elfo_core)) 18 | } 19 | 20 | /// Derives required traits to use the type as a message or a message part. 21 | /// 22 | /// Attributes: 23 | /// * `part` — do not derive `Message`. Useful for parts of messages. 24 | /// * `ret = SomeType` — also derive `Request` with the provided response type. 25 | /// * `name = "SomeName"` — override a message name. 26 | /// * `not(Debug)` — do not derive `Debug`. Useful for custom instances. 27 | /// * `not(Clone)` — the same for `Clone`. 28 | /// * `elfo = some::path` — override a path to elfo. 29 | #[proc_macro_attribute] 30 | pub fn message(attr: TokenStream, input: TokenStream) -> TokenStream { 31 | message_impl(attr, input, parse_quote!(::elfo)) 32 | } 33 | 34 | #[doc(hidden)] 35 | #[proc_macro_attribute] 36 | pub fn message_core(attr: TokenStream, input: TokenStream) -> TokenStream { 37 | message_impl(attr, input, parse_quote!(::elfo_core)) 38 | } 39 | -------------------------------------------------------------------------------- /elfo/release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {prerelease=true, file="../README.md", search="^elfo =(.*?)\".+?\"", replace="{{crate_name}} =${1}\"{{version}}\""}, 3 | {prerelease=true, file="../README.md", search="docs.rs/elfo/.*", replace="docs.rs/elfo/{{version}}/elfo"}, 4 | {prerelease=true, file="../CHANGELOG.md", search="Unreleased", replace="{{version}}"}, 5 | {prerelease=true, file="../CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, 6 | {prerelease=true, file="../CHANGELOG.md", search="ReleaseDate", replace="{{date}}"}, 7 | {prerelease=true, file="../CHANGELOG.md", search="", replace="\n\n## [Unreleased] - ReleaseDate", exactly=1}, 8 | {prerelease=true, file="../CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/elfo-rs/elfo/compare/{{tag_name}}...HEAD", exactly=1}, 9 | 10 | # Use precise versions (temporary for alpha releases!). TODO: remove once v0.2 is ready. 11 | # This is because even if `elfo = "=0.2.0-alpha.X"` is used, `cargo` fetches latest versions of crates (e.g. `elfo-core`). 12 | {prerelease=true, file="../elfo/Cargo.toml", search="^(.+version) = \"=0.2.0-alpha.*?\"", replace="$1 = \"={{version}}\""}, 13 | {prerelease=true, file="../elfo-macros/Cargo.toml", search="^(.+version) = \"=0.2.0-alpha.*?\"", replace="$1 = \"={{version}}\""}, 14 | ] 15 | -------------------------------------------------------------------------------- /elfo-logger/src/theme.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use tracing::Level; 4 | 5 | use elfo_core::{tracing::TraceId, ActorMeta}; 6 | use elfo_utils::time::SystemTime; 7 | 8 | use crate::formatters::*; 9 | 10 | pub(crate) trait Theme { 11 | type Timestamp: Formatter; 12 | type Level: Formatter; 13 | type TraceId: Formatter>; 14 | type ActorMeta: Formatter>>; 15 | type Payload: Formatter; 16 | type Location: Formatter<(&'static str, u32)>; 17 | type Module: Formatter; 18 | type ResetStyle: Formatter<()>; 19 | } 20 | 21 | pub(crate) struct PlainTheme; 22 | 23 | impl Theme for PlainTheme { 24 | type ActorMeta = EmptyIfNone>; 25 | type Level = Level; 26 | type Location = Location; 27 | type Module = Module; 28 | type Payload = Payload; 29 | type ResetStyle = DoNothing; 30 | type Timestamp = Rfc3339Weak; 31 | type TraceId = EmptyIfNone; 32 | } 33 | 34 | pub(crate) struct ColoredTheme; 35 | 36 | impl Theme for ColoredTheme { 37 | type ActorMeta = EmptyIfNone>>; 38 | type Level = ColoredLevel; 39 | type Location = ColoredLocation; 40 | type Module = ColoredModule; 41 | type Payload = ColoredPayload; 42 | type ResetStyle = ResetStyle; 43 | type Timestamp = Rfc3339Weak; 44 | type TraceId = EmptyIfNone>; 45 | } 46 | -------------------------------------------------------------------------------- /benches/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-benches" 3 | version = "0.0.0" 4 | publish = false 5 | 6 | repository.workspace = true 7 | authors.workspace = true 8 | license.workspace = true 9 | edition.workspace = true 10 | readme.workspace = true 11 | rust-version.workspace = true 12 | 13 | [dev-dependencies] 14 | elfo = { path = "../elfo" } 15 | elfo-utils = { version = "0.2.7", path = "../elfo-utils" } 16 | 17 | metrics.workspace = true 18 | tokio = { workspace = true, features = ["rt-multi-thread"] } 19 | derive_more.workspace = true 20 | criterion.workspace = true 21 | futures.workspace = true 22 | mimalloc = { version = "0.1.39", default-features = false } 23 | jemallocator = "0.5.4" 24 | tcmalloc = { version = "0.3.0", features = ["bundled"] } 25 | 26 | [[bench]] 27 | name = "messaging_sys" 28 | path = "messaging_sys.rs" 29 | harness = false 30 | 31 | [[bench]] 32 | name = "messaging_mi" 33 | path = "messaging_mi.rs" 34 | harness = false 35 | 36 | [[bench]] 37 | name = "messaging_je" 38 | path = "messaging_je.rs" 39 | harness = false 40 | 41 | [[bench]] 42 | name = "messaging_tc" 43 | path = "messaging_tc.rs" 44 | harness = false 45 | 46 | [[bench]] 47 | name = "coop" 48 | path = "coop.rs" 49 | harness = false 50 | 51 | [[bench]] 52 | name = "stream" 53 | path = "stream.rs" 54 | harness = false 55 | 56 | [[bench]] 57 | name = "trace_id" 58 | path = "trace_id.rs" 59 | harness = false 60 | 61 | [[bench]] 62 | name = "yield" 63 | path = "yield.rs" 64 | harness = false 65 | -------------------------------------------------------------------------------- /elfo-network/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-network" 3 | version = "0.2.0-alpha.20" 4 | description = "Distributed actors for elfo" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "network"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [features] 18 | turmoil06 = ["dep:turmoil06"] 19 | 20 | [dependencies] 21 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable", "network"] } 22 | elfo-utils = { version = "0.2.7", path = "../elfo-utils" } 23 | 24 | metrics.workspace = true 25 | dashmap.workspace = true 26 | derive_more.workspace = true 27 | eyre.workspace = true 28 | serde.workspace = true 29 | tokio = { workspace = true, features = ["net", "io-util"] } 30 | futures.workspace = true 31 | libc.workspace = true 32 | parking_lot.workspace = true 33 | slotmap.workspace = true 34 | pin-project.workspace = true 35 | tracing.workspace = true 36 | static_assertions = "1.1.0" 37 | fxhash = "0.2.1" 38 | humantime-serde = "1" 39 | kanal = "0.1.1" 40 | bitflags = "2.3.2" 41 | lz4_flex = { version = "0.11.1", default-features = false, features = ["std"] } 42 | byteorder = "1.4.3" 43 | turmoil06 = { package = "turmoil", version = "0.6", optional = true } 44 | 45 | [dev-dependencies] 46 | tracing-test = "0.2.4" # TODO: actually unused? 47 | proptest.workspace = true 48 | -------------------------------------------------------------------------------- /elfo-logger/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-logger" 3 | version = "0.2.0-alpha.20" 4 | description = "Logs events of the elfo system" 5 | keywords = ["elfo", "actor", "distributed", "tokio", "logging"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [features] 18 | tracing-log = [ "dep:tracing-log", "log" ] 19 | 20 | [dependencies] 21 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable"] } 22 | elfo-utils = { version = "0.2.7", path = "../elfo-utils" } 23 | 24 | metrics.workspace = true 25 | dashmap.workspace = true 26 | derive_more.workspace = true 27 | tokio = { workspace = true, features = ["macros", "fs", "io-util"] } 28 | tracing-subscriber = { workspace = true, features = ["env-filter", "parking_lot"] } 29 | bytesize.workspace = true 30 | parking_lot.workspace = true 31 | serde.workspace = true 32 | tracing.workspace = true 33 | arc-swap = "1.2.0" 34 | futures-intrusive = "0.5" 35 | sharded-slab = "0.1.7" 36 | tracing-log = { version = "0.2", optional = true } 37 | log = { version = "0.4.20", optional = true } 38 | fxhash = "0.2.1" 39 | humantime = "2.1.0" 40 | 41 | [dev-dependencies] 42 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["test-util"] } 43 | elfo-test = { path = "../elfo-test" } 44 | 45 | toml.workspace = true 46 | tempdir = "0.3.7" 47 | -------------------------------------------------------------------------------- /benches/stream.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, Instant}; 2 | 3 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 4 | 5 | use elfo::{message, stream::Stream, Context}; 6 | 7 | mod common; 8 | 9 | #[message] 10 | struct SomeEvent(usize); 11 | 12 | fn futures03(c: &mut Criterion) { 13 | async fn testee(mut ctx: Context, iter_count: u64) -> Duration { 14 | ctx.attach(Stream::from_futures03(futures::stream::iter( 15 | (0..).map(SomeEvent), 16 | ))); 17 | 18 | let start = Instant::now(); 19 | for _ in 0..iter_count { 20 | black_box(ctx.recv().await.unwrap()); 21 | } 22 | start.elapsed() 23 | } 24 | 25 | c.bench_function("futures03", |b| { 26 | b.iter_custom(|iter_count| common::bench_singleton(iter_count, testee)) 27 | }); 28 | } 29 | 30 | fn generate(c: &mut Criterion) { 31 | async fn testee(mut ctx: Context, iter_count: u64) -> Duration { 32 | ctx.attach(Stream::generate(move |mut e| async move { 33 | for i in 0.. { 34 | e.emit(SomeEvent(i)).await; 35 | } 36 | })); 37 | 38 | let start = Instant::now(); 39 | for _ in 0..iter_count { 40 | black_box(ctx.recv().await.unwrap()); 41 | } 42 | start.elapsed() 43 | } 44 | 45 | c.bench_function("generate", |b| { 46 | b.iter_custom(|iter_count| common::bench_singleton(iter_count, testee)) 47 | }); 48 | } 49 | 50 | criterion_group!(cases, futures03, generate); 51 | criterion_main!(cases); 52 | -------------------------------------------------------------------------------- /elfo-core/src/exec.rs: -------------------------------------------------------------------------------- 1 | use std::{error::Error, future::Future}; 2 | 3 | use sealed::sealed; 4 | 5 | pub(crate) trait Exec: Send + Sync + 'static { 6 | type Output: Future + Send + 'static; 7 | 8 | fn exec(&self, ctx: CTX) -> Self::Output; 9 | } 10 | 11 | pub(crate) type BoxedError = Box; 12 | 13 | impl Exec for F 14 | where 15 | F: Fn(CTX) -> O + Send + Sync + 'static, 16 | O: Future + Send + 'static, 17 | ER: ExecResult, 18 | { 19 | type Output = O; 20 | 21 | #[inline] 22 | fn exec(&self, ctx: CTX) -> O { 23 | self(ctx) 24 | } 25 | } 26 | 27 | #[sealed] 28 | pub trait ExecResult { 29 | fn unify(self) -> Result<(), BoxedError>; 30 | } 31 | 32 | #[sealed] 33 | impl ExecResult for () { 34 | fn unify(self) -> Result<(), BoxedError> { 35 | Ok(()) 36 | } 37 | } 38 | 39 | #[sealed] 40 | impl ExecResult for never::Never { 41 | #[allow(private_interfaces)] 42 | fn unify(self) -> Result<(), BoxedError> { 43 | self 44 | } 45 | } 46 | 47 | #[sealed] 48 | impl ExecResult for Result<(), E> 49 | where 50 | E: Into, 51 | { 52 | fn unify(self) -> Result<(), BoxedError> { 53 | self.map_err(Into::into) 54 | } 55 | } 56 | 57 | // === Never (!) === 58 | 59 | mod never { 60 | pub(super) type Never = ::Output; 61 | 62 | pub(super) trait HasOutput { 63 | type Output; 64 | } 65 | 66 | impl HasOutput for fn() -> O { 67 | type Output = O; 68 | } 69 | 70 | type F = fn() -> !; 71 | } 72 | -------------------------------------------------------------------------------- /elfo-telemeter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "elfo-telemeter" 3 | version = "0.2.0-alpha.20" 4 | description = "Collects and exposes metrics of the elfo system" 5 | keywords = ["elfo", "actor", "distributed", "tokio"] 6 | 7 | repository.workspace = true 8 | authors.workspace = true 9 | license.workspace = true 10 | edition.workspace = true 11 | readme.workspace = true 12 | rust-version.workspace = true 13 | 14 | [lints] 15 | workspace = true 16 | 17 | [[bench]] 18 | name = "telemetry" 19 | harness = false 20 | 21 | [features] 22 | unstable = [] 23 | 24 | [dependencies] 25 | elfo-core = { version = "0.2.0-alpha.20", path = "../elfo-core", features = ["unstable"] } 26 | 27 | stability.workspace = true 28 | metrics.workspace = true 29 | tokio = { workspace = true, features = ["net"] } 30 | thread_local.workspace = true 31 | parking_lot.workspace = true 32 | serde.workspace = true 33 | tracing.workspace = true 34 | hyper = { version = "1.1", features = ["server", "http1"] } 35 | hyper-util = { version = "0.1.3", features = ["tokio"] } 36 | http-body-util = "0.1" 37 | sketches-ddsketch = "0.3" 38 | seqlock = "0.2" 39 | fxhash = "0.2.1" 40 | humantime-serde = "1" 41 | cow-utils = "0.1.2" 42 | flate2 = "1" 43 | 44 | [dev-dependencies] 45 | elfo-test = { path = "../elfo-test" } 46 | elfo-configurer = { path = "../elfo-configurer" } 47 | 48 | tokio = { workspace = true, features = ["rt-multi-thread"] } 49 | toml.workspace = true 50 | criterion.workspace = true 51 | proptest.workspace = true 52 | eyre.workspace = true 53 | reqwest = { version = "0.12", default-features = false, features = ["gzip"] } 54 | -------------------------------------------------------------------------------- /elfo-telemeter/src/render.rs: -------------------------------------------------------------------------------- 1 | use fxhash::FxHashMap; 2 | use metrics::Label; 3 | 4 | use self::openmetrics::OpenMetricsRenderer; 5 | use crate::{ 6 | config::{Config, Quantile}, 7 | protocol::{Description, Snapshot}, 8 | }; 9 | 10 | mod openmetrics; 11 | 12 | #[derive(Default)] 13 | pub(crate) struct Renderer { 14 | quantiles: Vec<(Quantile, Label)>, 15 | global_labels: Vec