evno: High-Performance, Type-Safe Asynchronous Event Bus
English | 简体中文
evno is a high-performance, type-safe asynchronous event bus library built on the Tokio runtime. It leverages the low-latency, multicast ring buffer provided by gyre, combined with the structured concurrency of the acty Actor model, to deliver an event distribution system that features middleware capabilities and reliable lifecycle management.
Core Concepts and Features
evno is designed to provide a high-performance event system with robust and predictable lifecycle management.
1. Zero-Loss Startup Guarantee
evno ensures that event delivery does not begin until all listeners currently starting up have completed their subscription registration. This eliminates concerns about transient event loss due to asynchronous startup race conditions, guaranteeing that listeners always start receiving events from the initial point in the stream.
2. Structured Concurrency and Task Lifecycle
Every Listener started via Bus::bind runs in an independent asynchronous task with strict lifecycle control.
| Method | Description |
|---|---|
Bus::bind / Bus::on / Bus::once / Bus::many |
Starts a new event listening task. |
SubscribeHandle |
Provides explicit control for active task cancellation (cancel()) and waiting for completion (.await). |
CancellationToken |
Embedded within the Listener's handle method, enabling the Listener's internal logic to perform conditional self-cancellation. |
3. Type Safety and Event Chain (Chain/Step)
evno allows you to build event processing pipelines (middleware) using the Chain and Step Traits. A Step is responsible for transforming an event from type E_in to type E_out, enabling features like context injection, logging, or data normalization.
- Type Safety: The input and output types of the pipeline are determined at compile time, ensuring downstream listeners receive the expected, processed event type.
- Chaining: Use
chain.prepend(Step)to add a new processing step to the front of the existing chain.
4. Graceful Shutdown (Drain/Close)
Bus instances are cloneable, with all clones sharing the underlying event system and lifecycle state.
| Method | Semantics | Behavior |
|---|---|---|
bus.drain() |
Global Forced Drain. Consumes self. |
Blocks until 1. All Bus clones have been dropped, and 2. All running Listener tasks have completed processing and exited. |
bus.close() |
Conditional Graceful Shutdown. Consumes self. |
If the current Bus instance is the last remaining reference, it executes a full drain(). Otherwise, it only drops the current reference and returns immediately. |
Best Practice: When exiting the application, call close() on the objects holding Bus references. The system will automatically trigger a global drain only when the very last reference is released.
Getting Started and Tutorials
We will demonstrate the core usage of evno through a series of examples.
Adding Dependencies:
Add evno and tokio to your Cargo.toml.
[]
= "1"
= { = "1", = ["full"] }
1. Basic Event Dispatch
Define an event, start a continuous listener, and send events.
// main.rs
use ;
// 1. Define the event
;
async
2. Limit Listeners and Active Cancellation
Bus provides once (listen once) and many (listen N times) methods, as well as active cancellation via SubscribeHandle.
use ;
use ;
use Arc;
;
async
3. Middleware: Type-Safe Context Injection
Use Chain and Step to implement an event pipeline that injects context data before the event reaches the Bus.
use ;
use Arc;
use ;
// 1. Original event type
;
// 2. Injected context
// 3. Transformed event type
;
// 4. Define Step: Request ID Injector
;
async
4. Using to_emitter to Get a Typed Emitter
You can use to_emitter::<E>() to obtain a sender endpoint for a specific event type. This is convenient for encapsulating sending logic or integrating with other systems. If obtained from a Chain, the returned Emitter automatically applies all Step logic in the chain.
use ;
// Reuse RequestInjector and OriginalEvent definitions from the previous example
async
API Overview
| Trait / Struct | Description |
|---|---|
Bus |
The core event bus structure, used for event distribution and lifecycle management. |
Emit |
Generic sending Trait, allowing the sending of any type implementing Event (bus.emit(E)). |
TypedEmit |
Specific type sending Trait, used by type-fixed Emitters. |
Drain / Close |
Defines the asynchronous Traits for graceful bus shutdown and resource cleanup. |
Listener |
The Trait implemented by users to define event handling logic, including begin, handle, and after lifecycle hooks. |
Guard<E> |
The event data wrapper type, representing ownership, whose Drop behavior controls the release of underlying resources. |
SubscribeHandle |
The handle for a listener task, used for cancellation or waiting for completion. |
Chain |
The event processing pipeline structure, used to combine multiple Steps. |
Step |
The Trait defining event transformation logic, implementing event type modification. |
License
This project is licensed under the Apache 2.0 License.