use std::any::{Any, TypeId};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering};
use parking_lot::RwLock;
use tokio_util::sync::CancellationToken;
use crate::types::{Event, EventType, HookError, HookResult};
type ErasedEvent = dyn Any + Send + Sync;
type ErasedHandler = Arc<dyn Fn(CancellationToken, &ErasedEvent) -> HookResult + Send + Sync>;
type HandlerKey = (TypeId, EventType);
type HandlerMap = Arc<RwLock<HashMap<HandlerKey, Vec<(usize, ErasedHandler)>>>>;
pub struct HookRegistry {
handlers: HandlerMap,
event_types: Arc<RwLock<HashMap<TypeId, EventType>>>,
next_id: AtomicUsize,
}
impl std::fmt::Debug for HookRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let event_types = self.event_types.read();
let handlers = self.handlers.read();
let counts = handlers
.iter()
.map(|((type_id, registered_type), handlers)| {
let event_type = event_types
.get(type_id)
.unwrap_or(registered_type)
.to_string();
(event_type, handlers.len())
})
.collect::<HashMap<_, _>>();
f.debug_struct("HookRegistry")
.field("handler_counts", &counts)
.finish()
}
}
impl Default for HookRegistry {
fn default() -> Self {
Self::new()
}
}
impl HookRegistry {
#[must_use]
pub fn new() -> Self {
Self {
handlers: Arc::new(RwLock::new(HashMap::new())),
event_types: Arc::new(RwLock::new(HashMap::new())),
next_id: AtomicUsize::new(0),
}
}
pub fn on<E>(
&self,
event_type: EventType,
handler: impl Fn(CancellationToken, &E) -> HookResult + Send + Sync + 'static,
) -> Box<dyn FnOnce() + Send>
where
E: Event,
{
let type_id = TypeId::of::<E>();
let key = (type_id, event_type.clone());
self.event_types.write().insert(type_id, event_type);
let id = self.next_id.fetch_add(1, Ordering::SeqCst);
self.handlers.write().entry(key.clone()).or_default().push((
id,
Arc::new(move |cancel, event| {
let Some(event) = event.downcast_ref::<E>() else {
return Err(HookError::fatal("hook event type mismatch"));
};
handler(cancel, event)
}),
));
let handlers = Arc::clone(&self.handlers);
Box::new(move || {
if let Some(items) = handlers.write().get_mut(&key) {
items.retain(|(handler_id, _)| *handler_id != id);
}
})
}
pub fn emit<E>(&self, event: &E, cancel: CancellationToken) -> HookResult
where
E: Event,
{
let key = (TypeId::of::<E>(), event.event_type());
let handler_list = {
let handlers = self.handlers.read();
let Some(handler_list) = handlers.get(&key) else {
return Ok(());
};
handler_list
.iter()
.map(|(_, handler)| Arc::clone(handler))
.collect::<Vec<_>>()
};
let mut first_error: Option<HookError> = None;
for handler in handler_list {
if cancel.is_cancelled() {
let err = HookError::fatal("hook dispatch cancelled");
return Err(err);
}
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
handler(cancel.clone(), event)
}))
.unwrap_or_else(|payload| {
Err(HookError::new(format!(
"hook handler panicked: {}",
panic_message(payload)
)))
});
if let Err(err) = result {
if err.is_fatal() {
return Err(err);
}
if first_error.is_none() {
first_error = Some(err);
}
}
}
first_error.map_or(Ok(()), Err)
}
#[must_use]
pub fn has_handlers<E>(&self, event_type: &EventType) -> bool
where
E: Event,
{
self.handlers
.read()
.get(&(TypeId::of::<E>(), event_type.clone()))
.is_some_and(|handlers| !handlers.is_empty())
}
pub fn clear<E>(&self, event_type: &EventType)
where
E: Event,
{
self.handlers
.write()
.remove(&(TypeId::of::<E>(), event_type.clone()));
}
pub fn clear_all(&self) {
let mut handlers = self.handlers.write();
handlers.clear();
}
}
pub type LifecycleHookRegistry = HookRegistry;
fn panic_message(payload: Box<dyn std::any::Any + Send>) -> String {
if let Some(message) = payload.downcast_ref::<&str>() {
(*message).to_string()
} else if let Some(message) = payload.downcast_ref::<String>() {
message.clone()
} else {
"unknown panic payload".to_string()
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use tokio_util::sync::CancellationToken;
use super::HookRegistry;
use crate::{Event, EventType, HookError};
struct Ping;
impl Event for Ping {
fn event_type(&self) -> EventType {
EventType::new("ping")
}
}
struct Pong;
impl Event for Pong {
fn event_type(&self) -> EventType {
EventType::new("pong")
}
}
fn ping_type() -> EventType {
EventType::new("ping")
}
fn pong_type() -> EventType {
EventType::new("pong")
}
#[test]
fn register_and_emit() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let cloned = Arc::clone(&counter);
let _unsubscribe = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
assert!(registry.emit(&Ping, CancellationToken::new()).is_ok());
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn emit_no_handlers_returns_ok() {
assert!(
HookRegistry::new()
.emit(&Pong, CancellationToken::new())
.is_ok()
);
}
#[test]
fn non_fatal_error_does_not_short_circuit() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let _unsubscribe1 = registry.on::<Ping>(ping_type(), |_, _| Err(HookError::new("warn")));
let cloned = Arc::clone(&counter);
let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
let result = registry.emit(&Ping, CancellationToken::new());
assert_eq!(result.expect_err("error").message(), "warn");
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn fatal_error_short_circuits() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let _unsubscribe1 =
registry.on::<Ping>(ping_type(), |_, _| Err(HookError::fatal("blocked")));
let cloned = Arc::clone(&counter);
let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
assert!(
registry
.emit(&Ping, CancellationToken::new())
.expect_err("fatal")
.is_fatal()
);
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn cancelled_emit_errors_before_dispatch() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let cloned = Arc::clone(&counter);
let _unsubscribe = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
let token = CancellationToken::new();
token.cancel();
assert!(
registry
.emit(&Ping, token)
.expect_err("cancelled")
.is_fatal()
);
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn panicking_handler_becomes_error_and_continues() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let _unsubscribe1 = registry.on::<Ping>(ping_type(), |_, _| panic!("boom"));
let cloned = Arc::clone(&counter);
let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
assert!(registry.emit(&Ping, CancellationToken::new()).is_err());
assert_eq!(counter.load(Ordering::SeqCst), 1);
}
#[test]
fn unsubscribe_removes_handler() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
let cloned = Arc::clone(&counter);
struct ErrorEvent;
impl Event for ErrorEvent {
fn event_type(&self) -> EventType {
EventType::new("error")
}
}
let unsubscribe = registry.on::<ErrorEvent>(EventType::new("error"), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
unsubscribe();
let _ = registry.emit(&ErrorEvent, CancellationToken::new());
assert_eq!(counter.load(Ordering::SeqCst), 0);
}
#[test]
fn clear_removes_selected_event_types() {
let registry = HookRegistry::new();
struct A;
impl Event for A {
fn event_type(&self) -> EventType {
EventType::new("a")
}
}
struct B;
impl Event for B {
fn event_type(&self) -> EventType {
EventType::new("b")
}
}
let a = EventType::new("a");
let b = EventType::new("b");
let _u1 = registry.on::<A>(a.clone(), |_, _| Ok(()));
let _u2 = registry.on::<B>(b.clone(), |_, _| Ok(()));
registry.clear::<A>(&a);
assert!(!registry.has_handlers::<A>(&a));
assert!(registry.has_handlers::<B>(&b));
registry.clear_all();
assert!(!registry.has_handlers::<B>(&b));
}
#[test]
fn multiple_handlers_same_event_all_run() {
let registry = HookRegistry::new();
let counter = Arc::new(AtomicU32::new(0));
for _ in 0..3 {
let cloned = Arc::clone(&counter);
let _unsubscribe = registry.on::<Pong>(pong_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
}
let _ = registry.emit(&Pong, CancellationToken::new());
assert_eq!(counter.load(Ordering::SeqCst), 3);
}
#[test]
fn handler_can_mutate_registry_during_emit() {
let registry = Arc::new(HookRegistry::new());
let counter = Arc::new(AtomicU32::new(0));
let registry_for_handler = Arc::clone(®istry);
let _unsubscribe1 = registry.on::<Ping>(ping_type(), move |_, _| {
registry_for_handler.clear::<Ping>(&ping_type());
Ok(())
});
let cloned = Arc::clone(&counter);
let _unsubscribe2 = registry.on::<Ping>(ping_type(), move |_, _| {
cloned.fetch_add(1, Ordering::SeqCst);
Ok(())
});
registry
.emit(&Ping, CancellationToken::new())
.expect("emit");
assert_eq!(counter.load(Ordering::SeqCst), 1);
assert!(!registry.has_handlers::<Ping>(&ping_type()));
}
}