use std::sync::{Arc, RwLock};
use crate::atom::Atom;
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub struct HookEvent {
pub pid: u64,
pub module: Atom,
pub function: Atom,
pub arity: u8,
pub reductions_consumed: u32,
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum HookDecision {
Continue,
Suspend,
}
type HookCallback = dyn Fn(HookEvent) -> HookDecision + Send + Sync + 'static;
#[derive(Clone, Default)]
pub struct Hook {
callback: Arc<RwLock<Option<Arc<HookCallback>>>>,
}
impl Hook {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn register<F>(&self, callback: F)
where
F: Fn(HookEvent) -> HookDecision + Send + Sync + 'static,
{
let mut slot = self
.callback
.write()
.unwrap_or_else(|error| error.into_inner());
*slot = Some(Arc::new(callback));
}
pub fn unregister(&self) {
let mut slot = self
.callback
.write()
.unwrap_or_else(|error| error.into_inner());
*slot = None;
}
#[must_use]
pub fn is_registered(&self) -> bool {
self.callback
.read()
.unwrap_or_else(|error| error.into_inner())
.is_some()
}
#[must_use]
pub fn invoke(&self, event: HookEvent) -> HookDecision {
let callback = self
.callback
.read()
.unwrap_or_else(|error| error.into_inner())
.clone();
match callback {
Some(callback) => callback(event),
None => HookDecision::Continue,
}
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use super::{Hook, HookDecision, HookEvent};
use crate::atom::Atom;
fn event() -> HookEvent {
HookEvent {
pid: 7,
module: Atom::OK,
function: Atom::ERROR,
arity: 2,
reductions_consumed: 42,
}
}
#[test]
fn hook_register_replace_and_unregister_hook() {
let hook = Hook::new();
assert!(!hook.is_registered());
assert_eq!(hook.invoke(event()), HookDecision::Continue);
hook.register(|_| HookDecision::Suspend);
assert!(hook.is_registered());
assert_eq!(hook.invoke(event()), HookDecision::Suspend);
hook.register(|_| HookDecision::Continue);
assert_eq!(hook.invoke(event()), HookDecision::Continue);
hook.unregister();
assert!(!hook.is_registered());
assert_eq!(hook.invoke(event()), HookDecision::Continue);
}
#[test]
fn hook_receives_copied_metadata_at_yield() {
let hook = Hook::new();
let seen = Arc::new(Mutex::new(Vec::new()));
let seen_by_hook = Arc::clone(&seen);
hook.register(move |event| {
seen_by_hook
.lock()
.unwrap_or_else(|error| error.into_inner())
.push(event);
HookDecision::Continue
});
assert_eq!(hook.invoke(event()), HookDecision::Continue);
assert_eq!(
seen.lock()
.unwrap_or_else(|error| error.into_inner())
.as_slice(),
&[event()]
);
}
#[test]
fn unregistered_hook_does_not_call_previous_callback() {
let hook = Hook::new();
let calls = Arc::new(Mutex::new(0_u32));
let calls_by_hook = Arc::clone(&calls);
hook.register(move |_| {
*calls_by_hook
.lock()
.unwrap_or_else(|error| error.into_inner()) += 1;
HookDecision::Continue
});
hook.unregister();
assert_eq!(hook.invoke(event()), HookDecision::Continue);
assert_eq!(*calls.lock().unwrap_or_else(|error| error.into_inner()), 0);
}
}