use std::any::TypeId;
use std::collections::HashSet;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
use acton_ern::Ern;
use dashmap::DashMap;
use futures::future::join_all;
use tracing::{instrument, trace};
use crate::actor::{ActorConfig, Idle, ManagedActor};
use crate::common::{ActorHandle, ActorRuntime, BrokerRef};
use crate::message::{
BrokerRequest, BrokerRequestEnvelope, RemoveAllSubscriptions, RemoveSubscription,
SubscribeBroker,
};
use crate::traits::ActorHandleInterface;
#[cfg(feature = "ipc")]
use crate::common::ipc::{IpcPushNotification, IpcTypeRegistry, SubscriptionManager};
#[cfg(feature = "ipc")]
use parking_lot::RwLock;
#[derive(Default, Debug, Clone)]
pub struct Broker {
subscribers: Subscribers,
actor_handle: ActorHandle,
#[cfg(feature = "ipc")]
ipc_subscription_manager: Arc<RwLock<Option<Arc<SubscriptionManager>>>>,
#[cfg(feature = "ipc")]
ipc_type_registry: Arc<IpcTypeRegistry>,
}
type Subscribers = Arc<DashMap<TypeId, HashSet<(Ern, ActorHandle)>>>;
impl Deref for Broker {
type Target = ActorHandle;
#[inline]
fn deref(&self) -> &Self::Target {
&self.actor_handle
}
}
impl DerefMut for Broker {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.actor_handle
}
}
impl Broker {
#[instrument]
pub(crate) async fn initialize(runtime: ActorRuntime) -> BrokerRef {
let actor_config = ActorConfig::new(Ern::with_root("broker_main").unwrap(), None, None)
.expect("Couldn't create initial broker config");
assert!(
!runtime.0.cancellation_token.is_cancelled(),
"ActonInner cancellation_token must be present and active before creating ManagedActor in Broker::initialize"
);
let mut broker_actor: ManagedActor<Idle, Self> =
ManagedActor::new(Some(&runtime), Some(&actor_config));
#[cfg(feature = "ipc")]
{
broker_actor.model.ipc_subscription_manager =
runtime.0.ipc_subscription_manager.clone();
broker_actor.model.ipc_type_registry = runtime.0.ipc_type_registry.clone();
}
broker_actor
.mutate_on::<BrokerRequest>(|actor, event| {
trace!(message_type = ?event.message.message_type_id, "Broker received BrokerRequest");
let subscribers = actor.model.subscribers.clone(); let message_to_broadcast = event.message.clone();
#[cfg(feature = "ipc")]
let ipc_sub_mgr = actor.model.ipc_subscription_manager.clone();
#[cfg(feature = "ipc")]
let ipc_type_reg = actor.model.ipc_type_registry.clone();
Box::pin(async move {
Self::broadcast(subscribers, message_to_broadcast.clone()).await;
#[cfg(feature = "ipc")]
Self::forward_to_ipc(&ipc_sub_mgr, &ipc_type_reg, &message_to_broadcast);
})
})
.mutate_on::<SubscribeBroker>(|actor, event| {
let subscription_msg = event.message.clone();
let type_id = subscription_msg.message_type_id;
let subscriber_handle = subscription_msg.subscriber_context.clone();
let subscriber_id = subscription_msg.subscriber_id;
trace!(subscriber = %subscriber_id, message_type = ?type_id, "Broker received SubscribeBroker");
let subscribers_map = actor.model.subscribers.clone(); Box::pin(async move {
trace!(subscriber = %subscriber_id, message_type = ?type_id, "Subscription added");
subscribers_map
.entry(type_id)
.or_default() .insert((subscriber_id, subscriber_handle)); })
})
.mutate_on::<RemoveSubscription>(|actor, event| {
let unsubscription_msg = event.message.clone();
let type_id = unsubscription_msg.message_type_id;
let subscriber_handle = unsubscription_msg.subscriber_context.clone();
let subscriber_id = unsubscription_msg.subscriber_id;
trace!(subscriber = %subscriber_id, message_type = ?type_id, "Broker received RemoveSubscription");
let subscribers_map = actor.model.subscribers.clone(); Box::pin(async move {
Self::remove_subscription(
&subscribers_map,
type_id,
&(subscriber_id.clone(), subscriber_handle),
);
trace!(subscriber = %subscriber_id, message_type = ?type_id, "Subscription removed");
})
})
.mutate_on::<RemoveAllSubscriptions>(|actor, event| {
let subscriber_id = event.message.subscriber_id.clone();
trace!(subscriber = %subscriber_id, "Broker received RemoveAllSubscriptions");
let subscribers_map = actor.model.subscribers.clone(); Box::pin(async move {
Self::remove_all_subscriptions(&subscribers_map, &subscriber_id);
trace!(subscriber = %subscriber_id, "All subscriptions removed");
})
});
trace!("Starting the Broker actor...");
let mut broker_handle = broker_actor.start().await;
broker_handle.broker = Box::from(Some(broker_handle.clone()));
trace!("Broker started with handle ID: {}", broker_handle.id());
broker_handle
}
pub async fn broadcast(
subscribers: Subscribers, request: BrokerRequest,
) {
let message_type_id = request.message_type_id; trace!(message_type = ?message_type_id, "Broadcasting message");
if let Some(subscribers_set) = subscribers.get(&message_type_id) {
let num_subscribers = subscribers_set.len();
trace!(count = num_subscribers, message_type = ?message_type_id, "Found subscribers");
let shared_envelope: BrokerRequestEnvelope = request.into();
let mut futures = Vec::with_capacity(num_subscribers);
for (_, subscriber_handle) in subscribers_set.value() {
let handle = subscriber_handle.clone();
let envelope_to_send = shared_envelope.clone();
futures.push(async move {
trace!(subscriber = %handle.id(), message_type = ?message_type_id, "Sending broadcast");
handle.send(envelope_to_send).await;
});
}
join_all(futures).await;
trace!(count = num_subscribers, message_type = ?message_type_id, "Broadcast sends completed");
} else {
trace!(message_type = ?message_type_id, "No subscribers found for message type");
}
}
fn remove_subscription(
subscribers: &Subscribers,
type_id: TypeId,
subscriber: &(Ern, ActorHandle),
) {
if let Some(mut entry) = subscribers.get_mut(&type_id) {
entry.value_mut().remove(subscriber);
}
subscribers.remove_if(&type_id, |_, set| set.is_empty());
}
fn remove_all_subscriptions(subscribers: &Subscribers, subscriber_id: &Ern) {
subscribers.retain(|_, set| {
set.retain(|(id, _)| id != subscriber_id);
!set.is_empty()
});
}
#[cfg(feature = "ipc")]
fn forward_to_ipc(
ipc_sub_mgr: &Arc<RwLock<Option<Arc<SubscriptionManager>>>>,
ipc_type_reg: &Arc<IpcTypeRegistry>,
request: &BrokerRequest,
) {
let sub_mgr = {
let guard = ipc_sub_mgr.read();
guard.clone()
};
let Some(sub_mgr) = sub_mgr else {
trace!("No IPC subscription manager active, skipping IPC forward");
return;
};
let Some(type_name) = ipc_type_reg.get_type_name_by_id(&request.message_type_id) else {
trace!(type_id = ?request.message_type_id, "Type not registered for IPC, skipping forward");
return;
};
let payload_json = match ipc_type_reg
.serialize_by_type_id(&request.message_type_id, request.message.as_ref())
{
Ok(json) => json,
Err(e) => {
trace!(type_name, error = %e, "Failed to serialize payload for IPC forward");
return;
}
};
let notification = IpcPushNotification::new(type_name.clone(), None, payload_json);
sub_mgr.forward_to_subscribers(¬ification);
trace!(type_name, "Forwarded broadcast to IPC subscribers");
}
}
#[cfg(test)]
mod tests {
use super::*;
fn handle_with_id(id: &Ern) -> ActorHandle {
let (outbox, _inbox) = tokio::sync::mpsc::channel(8);
ActorHandle::new(id.clone(), outbox)
}
fn new_id(root: &str) -> Ern {
Ern::with_root(root).expect("valid ERN root")
}
fn insert_subscriber(subscribers: &Subscribers, type_id: TypeId, id: &Ern) {
subscribers
.entry(type_id)
.or_default()
.insert((id.clone(), handle_with_id(id)));
}
#[test]
fn remove_subscription_removes_only_the_matching_entry() {
let subscribers: Subscribers = Arc::default();
let type_id = TypeId::of::<u32>();
let first_id = new_id("first");
let second_id = new_id("second");
insert_subscriber(&subscribers, type_id, &first_id);
insert_subscriber(&subscribers, type_id, &second_id);
Broker::remove_subscription(
&subscribers,
type_id,
&(first_id.clone(), handle_with_id(&first_id)),
);
let remaining = subscribers.get(&type_id).expect("set should remain");
assert_eq!(remaining.len(), 1);
assert!(remaining.iter().all(|(id, _)| id == &second_id));
drop(remaining);
}
#[test]
fn remove_subscription_drops_empty_sets() {
let subscribers: Subscribers = Arc::default();
let type_id = TypeId::of::<u32>();
let subscriber_id = new_id("solo");
insert_subscriber(&subscribers, type_id, &subscriber_id);
Broker::remove_subscription(
&subscribers,
type_id,
&(subscriber_id.clone(), handle_with_id(&subscriber_id)),
);
assert!(
!subscribers.contains_key(&type_id),
"empty subscriber sets should be removed from the map"
);
}
#[test]
fn remove_all_subscriptions_removes_actor_from_every_type() {
let subscribers: Subscribers = Arc::default();
let first_type = TypeId::of::<u32>();
let second_type = TypeId::of::<String>();
let stopping_id = new_id("stopping");
let surviving_id = new_id("surviving");
insert_subscriber(&subscribers, first_type, &stopping_id);
insert_subscriber(&subscribers, second_type, &stopping_id);
insert_subscriber(&subscribers, second_type, &surviving_id);
Broker::remove_all_subscriptions(&subscribers, &stopping_id);
assert!(
!subscribers.contains_key(&first_type),
"sets left empty by the removal should be dropped"
);
let remaining = subscribers.get(&second_type).expect("set should remain");
assert_eq!(remaining.len(), 1);
assert!(remaining.iter().all(|(id, _)| id == &surviving_id));
drop(remaining);
}
}