use std::any::Any;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::time::Duration;
use dashmap::DashMap;
use tokio::sync::broadcast;
use tokio::task::AbortHandle;
use tokio::task::JoinHandle;
use crate::error::{EventBusError, PublishErrorReason, Result};
use crate::event::Event;
use crate::registry::EventRegistry;
use crate::execution_log::ExecutionLog;
use crate::telemetry::Telemetry;
type ErasedEvent = Arc<dyn Any + Send + Sync>;
type PublishCallback = Arc<dyn Fn(&str, serde_json::Value) + Send + Sync>;
#[derive(Clone)]
struct GlobalEvent {
event_name: String,
payload: ErasedEvent,
}
#[derive(Clone, Debug)]
pub enum RetryBackoff {
Fixed(Duration),
Exponential { base: Duration, max: Duration },
}
impl Default for RetryBackoff {
fn default() -> Self {
Self::Exponential {
base: Duration::from_millis(100),
max: Duration::from_secs(10),
}
}
}
#[derive(Clone, Debug)]
pub struct RetryPolicy {
pub max_retries: usize,
pub backoff: RetryBackoff,
pub timeout_per_attempt: Duration,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
max_retries: 0, backoff: RetryBackoff::default(),
timeout_per_attempt: Duration::from_secs(30),
}
}
}
impl RetryPolicy {
pub fn delay_for_attempt(&self, attempt: usize) -> Duration {
match &self.backoff {
RetryBackoff::Fixed(d) => *d,
RetryBackoff::Exponential { base, max } => {
let exp = 2u32.saturating_pow(attempt.min(16) as u32);
base.saturating_mul(exp as u32).min(*max)
}
}
}
}
pub trait DeadLetterHandler: Send + Sync + 'static {
fn on_dead_letter(&self, event_name: &str, attempts: usize, error: &str);
}
pub struct LoggingDeadLetterHandler;
impl DeadLetterHandler for LoggingDeadLetterHandler {
fn on_dead_letter(&self, event_name: &str, attempts: usize, error: &str) {
tracing::error!(
event = event_name,
attempts = attempts,
error = error,
"Event handler failed after all retries (dead letter)"
);
}
}
pub struct Subscription {
pub event_name: String,
pub id: usize,
abort_handle: AbortHandle,
inner: Arc<EventBusInner>,
}
impl std::fmt::Debug for Subscription {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Subscription")
.field("event_name", &self.event_name)
.field("id", &self.id)
.field("is_finished", &self.abort_handle.is_finished())
.finish()
}
}
impl Subscription {
pub fn unsubscribe(&self) {
self.abort_handle.abort();
self.inner.tasks.remove(&self.id);
}
pub fn is_finished(&self) -> bool {
self.abort_handle.is_finished()
}
}
pub struct EventBus {
inner: Arc<EventBusInner>,
}
struct EventBusInner {
channels: DashMap<String, broadcast::Sender<ErasedEvent>>,
global_channel: broadcast::Sender<GlobalEvent>,
topic_patterns: DashMap<String, Vec<String>>,
next_sub_id: AtomicUsize,
capacity: usize,
telemetry: Option<Arc<dyn Telemetry>>,
registry: Arc<EventRegistry>,
execution_log: Option<Arc<ExecutionLog>>,
publish_callbacks: RwLock<Vec<PublishCallback>>,
tasks: DashMap<usize, JoinHandle<()>>,
retry_policy: RetryPolicy,
dead_letter: Option<Arc<dyn DeadLetterHandler>>,
}
impl EventBus {
pub fn new() -> Self {
Self::from_builder(
1024, None, Arc::new(EventRegistry::new()), None,
RetryPolicy::default(), None,
)
}
pub fn with_capacity(capacity: usize) -> Self {
Self::from_builder(
capacity, None, Arc::new(EventRegistry::new()), None,
RetryPolicy::default(), None,
)
}
pub(crate) fn from_builder(
capacity: usize,
telemetry: Option<Arc<dyn Telemetry>>,
registry: Arc<EventRegistry>,
execution_log: Option<Arc<ExecutionLog>>,
retry_policy: RetryPolicy,
dead_letter: Option<Arc<dyn DeadLetterHandler>>,
) -> Self {
let (global_tx, _) = broadcast::channel(capacity);
Self {
inner: Arc::new(EventBusInner {
channels: DashMap::new(),
global_channel: global_tx,
topic_patterns: DashMap::new(),
next_sub_id: AtomicUsize::new(0),
capacity,
telemetry,
registry,
execution_log,
publish_callbacks: RwLock::new(Vec::new()),
tasks: DashMap::new(),
retry_policy,
dead_letter,
}),
}
}
pub fn builder() -> crate::builder::EventBusBuilder {
crate::builder::EventBusBuilder::new()
}
pub fn registry(&self) -> &Arc<EventRegistry> {
&self.inner.registry
}
pub fn execution_log(&self) -> Option<&Arc<ExecutionLog>> {
self.inner.execution_log.as_ref()
}
pub fn register_publish_callback(&self, callback: PublishCallback) {
self.inner.publish_callbacks.write().unwrap().push(callback);
}
fn get_or_create_channel<E: Event>(&self) -> broadcast::Sender<ErasedEvent> {
self.inner
.channels
.entry(E::event_name().to_string())
.or_insert_with(|| {
if !self.inner.registry.contains(E::event_name()) {
self.inner.registry.register_simple(E::event_name(), E::topic());
}
broadcast::channel(self.inner.capacity).0
})
.clone()
}
pub async fn publish<E: Event>(&self, event: E) -> Result<()> {
let start = std::time::Instant::now();
if !self.inner.registry.contains(E::event_name()) {
self.inner.registry.register_simple(E::event_name(), E::topic());
}
self.inner.registry.increment_publish_count(E::event_name());
let event_json = event.to_json();
let sender = {
match self.inner.channels.get(E::event_name()) {
Some(sender) if sender.receiver_count() > 0 => Some(sender.clone()),
Some(_) => {
tracing::debug!(
event = E::event_name(),
receivers = 0,
"Event published (no subscribers)"
);
None
}
None => {
tracing::debug!(
event = E::event_name(),
receivers = 0,
"Event published (no channel)"
);
None
}
}
};
if let Some(sender) = sender {
let payload: ErasedEvent = Arc::new(event.clone());
let receiver_count = sender.receiver_count();
if let Some(ref tel) = self.inner.telemetry {
tel.on_publish(E::event_name(), receiver_count);
}
sender
.send(payload)
.map_err(|e| EventBusError::PublishFailed {
event_name: E::event_name(),
reason: PublishErrorReason::ChannelError(e.to_string()),
})?;
tracing::debug!(
event = E::event_name(),
receivers = receiver_count,
"Event published"
);
if let Some(ref tel) = self.inner.telemetry {
tel.on_publish_complete(E::event_name(), start.elapsed());
}
let global_event = GlobalEvent {
event_name: E::event_name().to_string(),
payload: Arc::new(event) as ErasedEvent,
};
let _ = self.inner.global_channel.send(global_event);
} else {
let global_event = GlobalEvent {
event_name: E::event_name().to_string(),
payload: Arc::new(event) as ErasedEvent,
};
let _ = self.inner.global_channel.send(global_event);
}
if let Some(json) = event_json {
let callbacks = self.inner.publish_callbacks.read().unwrap();
for cb in callbacks.iter() {
cb(E::event_name(), json.clone());
}
}
Ok(())
}
pub async fn subscribe<E, F, Fut>(&self, handler: F) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
self.subscribe_internal(handler, self.inner.retry_policy.clone()).await
}
pub async fn subscribe_with_retry<E, F, Fut>(
&self,
handler: F,
retry_policy: RetryPolicy,
) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
self.subscribe_internal(handler, retry_policy).await
}
async fn subscribe_internal<E, F, Fut>(
&self,
handler: F,
retry_policy: RetryPolicy,
) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
let sender = self.get_or_create_channel::<E>();
let mut rx = sender.subscribe();
let event_name = E::event_name().to_string();
let id = self.inner.next_sub_id.fetch_add(1, Ordering::Relaxed);
let sub_count = sender.receiver_count();
self.inner.registry.set_subscriber_count(&event_name, sub_count);
let telemetry = self.inner.telemetry.clone();
let dead_letter = self.inner.dead_letter.clone();
let handler_event_name = event_name.clone();
let handle: JoinHandle<()> = tokio::spawn(async move {
loop {
match rx.recv().await {
Ok(payload) => {
match payload.downcast_ref::<E>() {
Some(event) => {
if let Some(ref tel) = telemetry {
tel.on_handler_start(&handler_event_name, id);
}
let handler_start = std::time::Instant::now();
let mut last_error_str = None;
let mut success = false;
for attempt in 0..=retry_policy.max_retries {
if attempt > 0 {
let delay = retry_policy.delay_for_attempt(attempt - 1);
tracing::debug!(
event = %handler_event_name,
attempt = attempt + 1,
delay_ms = delay.as_millis(),
"Retrying handler"
);
tokio::time::sleep(delay).await;
}
match tokio::time::timeout(
retry_policy.timeout_per_attempt,
handler(event.clone()),
).await {
Ok(Ok(())) => {
success = true;
break;
}
Ok(Err(e)) => {
last_error_str = Some(e.to_string());
tracing::warn!(
event = %handler_event_name,
attempt = attempt + 1,
max_retries = retry_policy.max_retries,
error = %e,
"Handler failed"
);
}
Err(_) => {
last_error_str = Some("Handler timeout".to_string());
tracing::warn!(
event = %handler_event_name,
attempt = attempt + 1,
"Handler timed out"
);
}
}
}
let handler_elapsed = handler_start.elapsed();
if !success {
if let Some(ref dl) = dead_letter {
dl.on_dead_letter(
&handler_event_name,
retry_policy.max_retries + 1,
last_error_str.as_deref().unwrap_or("unknown"),
);
}
}
if let Some(ref tel) = telemetry {
let err_str = if success {
None
} else {
last_error_str
};
tel.on_handler_complete(
&handler_event_name,
id,
handler_elapsed,
err_str.as_deref(),
);
}
}
None => {
tracing::error!(
event = %handler_event_name,
"Failed to downcast event"
);
}
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!(
event = %handler_event_name,
lagged = n,
"Subscriber lagged behind"
);
if let Some(ref tel) = telemetry {
tel.on_handler_lagged(&handler_event_name, id, n as usize);
}
}
Err(broadcast::error::RecvError::Closed) => {
tracing::debug!(
event = %handler_event_name,
"Channel closed, stopping subscriber"
);
break;
}
}
}
});
let abort_handle = handle.abort_handle();
self.inner.tasks.insert(id, handle);
if let Some(ref tel) = self.inner.telemetry {
tel.on_subscribe(&event_name, id);
}
Ok(Subscription {
event_name,
id,
abort_handle,
inner: self.inner.clone(),
})
}
pub async fn subscribe_pattern<E, F, Fut>(
&self,
pattern: &str,
handler: F,
) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
self.subscribe_pattern_internal(pattern, handler, self.inner.retry_policy.clone()).await
}
pub async fn subscribe_pattern_with_retry<E, F, Fut>(
&self,
pattern: &str,
handler: F,
retry_policy: RetryPolicy,
) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
self.subscribe_pattern_internal(pattern, handler, retry_policy).await
}
async fn subscribe_pattern_internal<E, F, Fut>(
&self,
pattern: &str,
handler: F,
retry_policy: RetryPolicy,
) -> Result<Subscription>
where
E: Event,
F: Fn(E) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<()>> + Send + 'static,
{
self.inner
.topic_patterns
.entry(pattern.to_string())
.or_default()
.push(E::event_name().to_string());
let mut rx = self.inner.global_channel.subscribe();
let event_name = E::event_name().to_string();
let id = self.inner.next_sub_id.fetch_add(1, Ordering::Relaxed);
let pattern_owned = pattern.to_string();
let handler_event_name = event_name.clone();
let telemetry = self.inner.telemetry.clone();
let dead_letter = self.inner.dead_letter.clone();
let handle: JoinHandle<()> = tokio::spawn(async move {
loop {
match rx.recv().await {
Ok(global_event) => {
if !crate::topic::matches(&pattern_owned, &global_event.event_name) {
continue;
}
match global_event.payload.downcast_ref::<E>() {
Some(event) => {
if let Some(ref tel) = telemetry {
tel.on_handler_start(&handler_event_name, id);
}
let handler_start = std::time::Instant::now();
let mut last_error_str = None;
let mut success = false;
for attempt in 0..=retry_policy.max_retries {
if attempt > 0 {
let delay = retry_policy.delay_for_attempt(attempt - 1);
tracing::debug!(
event = %handler_event_name,
pattern = %pattern_owned,
attempt = attempt + 1,
"Retrying pattern handler"
);
tokio::time::sleep(delay).await;
}
match tokio::time::timeout(
retry_policy.timeout_per_attempt,
handler(event.clone()),
).await {
Ok(Ok(())) => {
success = true;
break;
}
Ok(Err(e)) => {
last_error_str = Some(e.to_string());
tracing::warn!(
event = %handler_event_name,
pattern = %pattern_owned,
attempt = attempt + 1,
error = %e,
"Pattern handler failed"
);
}
Err(_) => {
last_error_str = Some("Handler timeout".to_string());
}
}
}
let handler_elapsed = handler_start.elapsed();
if !success {
if let Some(ref dl) = dead_letter {
dl.on_dead_letter(
&handler_event_name,
retry_policy.max_retries + 1,
last_error_str.as_deref().unwrap_or("unknown"),
);
}
}
if let Some(ref tel) = telemetry {
let err_str = if success { None } else { last_error_str };
tel.on_handler_complete(
&handler_event_name,
id,
handler_elapsed,
err_str.as_deref(),
);
}
}
None => {
}
}
}
Err(broadcast::error::RecvError::Lagged(n)) => {
tracing::warn!(
pattern = %pattern_owned,
lagged = n,
"Pattern subscriber lagged behind"
);
if let Some(ref tel) = telemetry {
tel.on_handler_lagged(&handler_event_name, id, n as usize);
}
}
Err(broadcast::error::RecvError::Closed) => {
tracing::debug!(
pattern = %pattern_owned,
"Global channel closed, stopping pattern subscriber"
);
break;
}
}
}
});
let abort_handle = handle.abort_handle();
self.inner.tasks.insert(id, handle);
if let Some(ref tel) = self.inner.telemetry {
tel.on_subscribe(&event_name, id);
}
Ok(Subscription {
event_name,
id,
abort_handle,
inner: self.inner.clone(),
})
}
pub fn shutdown(&self) {
let task_ids: Vec<usize> = self.inner.tasks.iter().map(|e| *e.key()).collect();
for id in task_ids {
if let Some((_, handle)) = self.inner.tasks.remove(&id) {
handle.abort();
}
}
self.inner.channels.clear();
}
pub async fn shutdown_graceful(&self, timeout: std::time::Duration) -> usize {
self.inner.channels.clear();
let tasks: Vec<(usize, JoinHandle<()>)> = {
let mut handles = Vec::new();
let keys: Vec<usize> = self.inner.tasks.iter().map(|e| *e.key()).collect();
for key in keys {
if let Some(entry) = self.inner.tasks.remove(&key) {
handles.push(entry);
}
}
handles
};
let total = tasks.len();
let result = tokio::time::timeout(timeout, async {
for (_, handle) in tasks {
let _ = handle.await;
}
})
.await;
if result.is_ok() {
0
} else {
total
}
}
}
impl Default for EventBus {
fn default() -> Self {
Self::new()
}
}
impl Clone for EventBus {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}