use std::any::{Any, TypeId};
use std::collections::{BTreeMap, HashMap, VecDeque};
use std::sync::{Arc, Mutex};
use std::time::Duration;
use tokio::sync::{mpsc, oneshot};
use serde::{Deserialize, Serialize};
use crate::actor::{Actor, ActorContext, RemoteDispatch};
use crate::endpoint::Endpoint;
use crate::instrument;
use crate::lifecycle::{
ActorFactory, ActorTerminated, RestartConfig, RestartPolicy, SystemSignal, TerminateHook,
TerminationReason, WatchEntry,
};
use crate::listing::{
ErasedListingSender, ErasedReceptionKey, ErasedWatchedListingSender, Listing, ReceptionKey,
TypedListingSender, TypedWatchedListingSender, WatchedListing,
};
use crate::oplog::{Op, OpLog, OpType, VersionVector};
use crate::ready::ReadyHandle;
use crate::runtime::{Runtime, SpawnHandle, TokioRuntime};
use crate::supervisor::{run_supervisor, should_restart};
use crate::wire::{DispatchRequest, EnvelopeProxy, RemoteInvocation, ResponseRegistry};
#[derive(Debug, Clone)]
pub enum ActorEvent {
Registered {
label: String,
actor_type: String,
visibility: Visibility,
},
Deregistered {
label: String,
actor_type: String,
},
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub enum Visibility {
Public,
#[default]
Internal,
Private,
}
pub(crate) trait ErasedEndpointFactory: Send + Sync {
fn create_endpoint_any(&self) -> Box<dyn Any + Send>;
}
pub(crate) struct LocalEndpointFactory<A: Actor> {
mailbox_tx: mpsc::UnboundedSender<Box<dyn EnvelopeProxy<A>>>,
}
impl<A: Actor + 'static> ErasedEndpointFactory for LocalEndpointFactory<A> {
fn create_endpoint_any(&self) -> Box<dyn Any + Send> {
Box::new(Endpoint::<A>::local(self.mailbox_tx.clone()))
}
}
pub(crate) struct ActorEntry {
pub(crate) label: String,
pub(crate) type_id: TypeId,
pub(crate) actor_type_name: String,
pub(crate) location: EntryLocation,
pub(crate) keys: Vec<ErasedReceptionKey>,
pub(crate) node_id: String,
#[allow(dead_code)]
pub(crate) visibility: Visibility,
}
pub(crate) enum EntryLocation {
Local {
endpoint_factory: Box<dyn ErasedEndpointFactory>,
dispatch_tx: mpsc::UnboundedSender<DispatchRequest>,
shutdown_tx: Option<oneshot::Sender<()>>,
},
Remote {
wire_tx: mpsc::UnboundedSender<RemoteInvocation>,
response_registry: ResponseRegistry,
},
}
#[derive(Debug, Clone)]
pub struct ReceptionistConfig {
pub node_id: String,
pub origin_addr: String,
pub flush_interval: Option<Duration>,
pub blip_window: Option<Duration>,
}
impl Default for ReceptionistConfig {
fn default() -> Self {
Self {
node_id: "local".to_string(),
origin_addr: "127.0.0.1:0".to_string(),
flush_interval: None,
blip_window: None,
}
}
}
pub(crate) struct DeregisterGuard {
pub(crate) receptionist: Receptionist,
pub(crate) label: String,
pub(crate) reason: Arc<Mutex<TerminationReason>>,
}
impl Drop for DeregisterGuard {
fn drop(&mut self) {
let reason = self.reason.lock().unwrap().clone();
self.receptionist
.deregister_with_reason(&self.label, reason);
}
}
struct PendingListingNotification {
label: String,
key_id: String,
type_id: TypeId,
}
struct ReceptionistInner {
config: ReceptionistConfig,
entries: Mutex<BTreeMap<String, ActorEntry>>,
event_subscribers: Mutex<Vec<mpsc::UnboundedSender<ActorEvent>>>,
listing_subscribers: Mutex<Vec<Box<dyn ErasedListingSender>>>,
watched_listing_subscribers: Mutex<Vec<Box<dyn ErasedWatchedListingSender>>>,
oplog: Mutex<OpLog>,
observed_versions: Mutex<VersionVector>,
watches: Mutex<HashMap<String, Vec<WatchEntry>>>,
pending_notifications: Mutex<Vec<PendingListingNotification>>,
blip_pending: Mutex<HashMap<String, SpawnHandle>>,
runtime: Arc<dyn Runtime>,
terminate_hook: Mutex<Option<Arc<dyn TerminateHook>>>,
}
#[derive(Clone)]
pub struct Receptionist {
inner: Arc<ReceptionistInner>,
}
impl Receptionist {
pub fn new() -> Self {
Self::with_config(ReceptionistConfig::default())
}
pub fn with_node_id(node_id: impl Into<String>) -> Self {
Self::with_config(ReceptionistConfig {
node_id: node_id.into(),
..Default::default()
})
}
pub fn with_config(config: ReceptionistConfig) -> Self {
Self::with_config_and_runtime(config, Arc::new(TokioRuntime))
}
pub fn with_config_and_runtime(config: ReceptionistConfig, runtime: Arc<dyn Runtime>) -> Self {
let node_id = config.node_id.clone();
let flush_interval = config.flush_interval;
let receptionist = Self {
inner: Arc::new(ReceptionistInner {
config,
entries: Mutex::new(BTreeMap::new()),
event_subscribers: Mutex::new(Vec::new()),
listing_subscribers: Mutex::new(Vec::new()),
watched_listing_subscribers: Mutex::new(Vec::new()),
oplog: Mutex::new(OpLog::new(node_id)),
observed_versions: Mutex::new(VersionVector::new()),
watches: Mutex::new(HashMap::new()),
pending_notifications: Mutex::new(Vec::new()),
blip_pending: Mutex::new(HashMap::new()),
runtime,
terminate_hook: Mutex::new(None),
}),
};
if let Some(interval) = flush_interval {
let r = receptionist.clone();
let runtime = receptionist.inner.runtime.clone();
receptionist.inner.runtime.spawn(Box::pin(async move {
loop {
runtime.sleep(interval).await;
r.flush_pending_notifications();
}
}));
}
receptionist
}
pub fn runtime(&self) -> &Arc<dyn Runtime> {
&self.inner.runtime
}
pub fn set_terminate_hook(&self, hook: Option<Arc<dyn TerminateHook>>) {
*self.inner.terminate_hook.lock().unwrap() = hook;
}
pub(crate) fn terminate_hook(&self) -> Option<Arc<dyn TerminateHook>> {
self.inner.terminate_hook.lock().unwrap().clone()
}
pub fn node_id(&self) -> &str {
&self.inner.config.node_id
}
pub fn start<A>(&self, label: &str, actor: A, state: A::State) -> Endpoint<A>
where
A: Actor + RemoteDispatch + 'static,
{
let (mailbox_tx, mailbox_rx) = mpsc::unbounded_channel::<Box<dyn EnvelopeProxy<A>>>();
let (dispatch_tx, dispatch_rx) = mpsc::unbounded_channel::<DispatchRequest>();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (system_tx, system_rx) = mpsc::unbounded_channel::<SystemSignal>();
let exit_reason = Arc::new(Mutex::new(TerminationReason::Stopped));
let guard = DeregisterGuard {
receptionist: self.clone(),
label: label.to_string(),
reason: exit_reason.clone(),
};
let ctx = ActorContext {
label: label.to_string(),
node_id: self.inner.config.node_id.clone(),
mailbox_tx: mailbox_tx.clone(),
receptionist: self.clone(),
system_tx,
reply_token: std::sync::Mutex::new(None),
};
self.inner.runtime.spawn(Box::pin(async move {
let _ = run_supervisor(
actor,
state,
ctx,
mailbox_rx,
dispatch_rx,
shutdown_rx,
system_rx,
exit_reason,
guard,
)
.await;
}));
let endpoint = Endpoint::local(mailbox_tx.clone());
let entry = ActorEntry {
label: label.to_string(),
type_id: TypeId::of::<A>(),
actor_type_name: std::any::type_name::<A>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory { mailbox_tx }),
dispatch_tx,
shutdown_tx: Some(shutdown_tx),
},
keys: Vec::new(),
node_id: self.inner.config.node_id.clone(),
visibility: Visibility::Internal,
};
self.inner
.entries
.lock()
.unwrap()
.insert(label.to_string(), entry);
self.record_register_op(label, std::any::type_name::<A>(), &[], Visibility::Internal);
instrument::actor_started(std::any::type_name::<A>());
instrument::receptionist_registration();
self.emit(ActorEvent::Registered {
label: label.to_string(),
actor_type: std::any::type_name::<A>().to_string(),
visibility: Visibility::Internal,
});
endpoint
}
pub fn start_public<A>(&self, label: &str, actor: A, state: A::State) -> Endpoint<A>
where
A: Actor + RemoteDispatch + 'static,
{
self.start_with_visibility(label, actor, state, Visibility::Public)
}
pub fn start_private<A>(&self, label: &str, actor: A, state: A::State) -> Endpoint<A>
where
A: Actor + RemoteDispatch + 'static,
{
self.start_with_visibility(label, actor, state, Visibility::Private)
}
fn start_with_visibility<A>(
&self,
label: &str,
actor: A,
state: A::State,
visibility: Visibility,
) -> Endpoint<A>
where
A: Actor + RemoteDispatch + 'static,
{
let (mailbox_tx, mailbox_rx) = mpsc::unbounded_channel::<Box<dyn EnvelopeProxy<A>>>();
let (dispatch_tx, dispatch_rx) = mpsc::unbounded_channel::<DispatchRequest>();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (system_tx, system_rx) = mpsc::unbounded_channel::<SystemSignal>();
let exit_reason = Arc::new(Mutex::new(TerminationReason::Stopped));
let guard = DeregisterGuard {
receptionist: self.clone(),
label: label.to_string(),
reason: exit_reason.clone(),
};
let ctx = ActorContext {
label: label.to_string(),
node_id: self.inner.config.node_id.clone(),
mailbox_tx: mailbox_tx.clone(),
receptionist: self.clone(),
system_tx,
reply_token: std::sync::Mutex::new(None),
};
self.inner.runtime.spawn(Box::pin(async move {
let _ = run_supervisor(
actor,
state,
ctx,
mailbox_rx,
dispatch_rx,
shutdown_rx,
system_rx,
exit_reason,
guard,
)
.await;
}));
let endpoint = Endpoint::local(mailbox_tx.clone());
let entry = ActorEntry {
label: label.to_string(),
type_id: TypeId::of::<A>(),
actor_type_name: std::any::type_name::<A>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory { mailbox_tx }),
dispatch_tx,
shutdown_tx: Some(shutdown_tx),
},
keys: Vec::new(),
node_id: self.inner.config.node_id.clone(),
visibility,
};
self.inner
.entries
.lock()
.unwrap()
.insert(label.to_string(), entry);
self.record_register_op(label, std::any::type_name::<A>(), &[], visibility);
instrument::actor_started(std::any::type_name::<A>());
instrument::receptionist_registration();
self.emit(ActorEvent::Registered {
label: label.to_string(),
actor_type: std::any::type_name::<A>().to_string(),
visibility,
});
endpoint
}
pub fn prepare<A>(
&self,
label: &str,
actor: A,
state: A::State,
) -> (Endpoint<A>, ReadyHandle<A>)
where
A: Actor + RemoteDispatch + 'static,
{
let (mailbox_tx, mailbox_rx) = mpsc::unbounded_channel::<Box<dyn EnvelopeProxy<A>>>();
let (dispatch_tx, dispatch_rx) = mpsc::unbounded_channel::<DispatchRequest>();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (system_tx, system_rx) = mpsc::unbounded_channel::<SystemSignal>();
let exit_reason = Arc::new(Mutex::new(TerminationReason::Stopped));
let guard = DeregisterGuard {
receptionist: self.clone(),
label: label.to_string(),
reason: exit_reason.clone(),
};
let ctx = ActorContext {
label: label.to_string(),
node_id: self.inner.config.node_id.clone(),
mailbox_tx: mailbox_tx.clone(),
receptionist: self.clone(),
system_tx,
reply_token: std::sync::Mutex::new(None),
};
let endpoint = Endpoint::local(mailbox_tx.clone());
let entry = ActorEntry {
label: label.to_string(),
type_id: TypeId::of::<A>(),
actor_type_name: std::any::type_name::<A>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory { mailbox_tx }),
dispatch_tx,
shutdown_tx: Some(shutdown_tx),
},
keys: Vec::new(),
node_id: self.inner.config.node_id.clone(),
visibility: Visibility::Internal,
};
self.inner
.entries
.lock()
.unwrap()
.insert(label.to_string(), entry);
self.record_register_op(label, std::any::type_name::<A>(), &[], Visibility::Internal);
instrument::actor_started(std::any::type_name::<A>());
instrument::receptionist_registration();
self.emit(ActorEvent::Registered {
label: label.to_string(),
actor_type: std::any::type_name::<A>().to_string(),
visibility: Visibility::Internal,
});
let ready = ReadyHandle::new(
actor,
state,
ctx,
mailbox_rx,
dispatch_rx,
shutdown_rx,
system_rx,
exit_reason,
guard,
);
(endpoint, ready)
}
pub fn start_with_policy<F: ActorFactory>(
&self,
label: &str,
factory: F,
policy: RestartPolicy,
) -> Endpoint<F::Actor> {
self.start_with_config(
label,
factory,
RestartConfig {
policy,
..Default::default()
},
)
}
pub fn start_with_config<F: ActorFactory>(
&self,
label: &str,
mut factory: F,
config: RestartConfig,
) -> Endpoint<F::Actor> {
let (mailbox_tx, mut mailbox_rx) =
mpsc::unbounded_channel::<Box<dyn EnvelopeProxy<F::Actor>>>();
let (dispatch_tx, mut dispatch_rx) = mpsc::unbounded_channel::<DispatchRequest>();
let endpoint = Endpoint::local(mailbox_tx.clone());
let (actor, state) = factory.create();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (system_tx, system_rx) = mpsc::unbounded_channel::<SystemSignal>();
let exit_reason = Arc::new(Mutex::new(TerminationReason::Stopped));
let guard = DeregisterGuard {
receptionist: self.clone(),
label: label.to_string(),
reason: exit_reason.clone(),
};
let ctx = ActorContext {
label: label.to_string(),
node_id: self.inner.config.node_id.clone(),
mailbox_tx: mailbox_tx.clone(),
receptionist: self.clone(),
system_tx,
reply_token: std::sync::Mutex::new(None),
};
let entry = ActorEntry {
label: label.to_string(),
type_id: TypeId::of::<F::Actor>(),
actor_type_name: std::any::type_name::<F::Actor>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory {
mailbox_tx: mailbox_tx.clone(),
}),
dispatch_tx: dispatch_tx.clone(),
shutdown_tx: Some(shutdown_tx),
},
keys: Vec::new(),
node_id: self.inner.config.node_id.clone(),
visibility: Visibility::Internal,
};
self.inner
.entries
.lock()
.unwrap()
.insert(label.to_string(), entry);
self.record_register_op(
label,
std::any::type_name::<F::Actor>(),
&[],
Visibility::Internal,
);
instrument::actor_started(std::any::type_name::<F::Actor>());
instrument::receptionist_registration();
self.emit(ActorEvent::Registered {
label: label.to_string(),
actor_type: std::any::type_name::<F::Actor>().to_string(),
visibility: Visibility::Internal,
});
let receptionist = self.clone();
let label_owned = label.to_string();
let node_id = self.inner.config.node_id.clone();
let runtime = self.inner.runtime.clone();
self.inner.runtime.spawn(Box::pin(async move {
let mut restart_times: VecDeque<Duration> = VecDeque::new();
let mut backoff_duration = config.backoff.initial;
let (mb, dp) = run_supervisor(
actor,
state,
ctx,
mailbox_rx,
dispatch_rx,
shutdown_rx,
system_rx,
exit_reason.clone(),
guard,
)
.await;
mailbox_rx = mb;
dispatch_rx = dp;
let reason = exit_reason.lock().unwrap().clone();
if !should_restart(&config.policy, &reason) {
return;
}
loop {
let now = runtime.now();
while restart_times
.front()
.is_some_and(|t| now.saturating_sub(*t) > config.window)
{
restart_times.pop_front();
}
if restart_times.len() >= config.max_restarts as usize {
instrument::actor_restart_limit_exceeded(std::any::type_name::<F::Actor>());
let entry = ActorEntry {
label: label_owned.clone(),
type_id: TypeId::of::<F::Actor>(),
actor_type_name: std::any::type_name::<F::Actor>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory {
mailbox_tx: mailbox_tx.clone(),
}),
dispatch_tx: dispatch_tx.clone(),
shutdown_tx: None,
},
keys: Vec::new(),
node_id: node_id.clone(),
visibility: Visibility::Internal,
};
receptionist
.inner
.entries
.lock()
.unwrap()
.insert(label_owned.clone(), entry);
receptionist.deregister_with_reason(
&label_owned,
TerminationReason::RestartLimitExceeded,
);
break;
}
restart_times.push_back(now);
runtime.sleep(backoff_duration).await;
backoff_duration = Duration::from_secs_f64(
(backoff_duration.as_secs_f64() * config.backoff.multiplier)
.min(config.backoff.max.as_secs_f64()),
);
let (actor, state) = factory.create();
let (shutdown_tx, shutdown_rx) = oneshot::channel::<()>();
let (system_tx, system_rx) = mpsc::unbounded_channel::<SystemSignal>();
let exit_reason = Arc::new(Mutex::new(TerminationReason::Stopped));
let guard = DeregisterGuard {
receptionist: receptionist.clone(),
label: label_owned.clone(),
reason: exit_reason.clone(),
};
let ctx = ActorContext {
label: label_owned.clone(),
node_id: node_id.clone(),
mailbox_tx: mailbox_tx.clone(),
receptionist: receptionist.clone(),
system_tx,
reply_token: std::sync::Mutex::new(None),
};
let entry = ActorEntry {
label: label_owned.clone(),
type_id: TypeId::of::<F::Actor>(),
actor_type_name: std::any::type_name::<F::Actor>().to_string(),
location: EntryLocation::Local {
endpoint_factory: Box::new(LocalEndpointFactory {
mailbox_tx: mailbox_tx.clone(),
}),
dispatch_tx: dispatch_tx.clone(),
shutdown_tx: Some(shutdown_tx),
},
keys: Vec::new(),
node_id: node_id.clone(),
visibility: Visibility::Internal,
};
receptionist
.inner
.entries
.lock()
.unwrap()
.insert(label_owned.clone(), entry);
instrument::actor_restarted(std::any::type_name::<F::Actor>());
instrument::receptionist_registration();
receptionist.emit(ActorEvent::Registered {
label: label_owned.clone(),
actor_type: std::any::type_name::<F::Actor>().to_string(),
visibility: Visibility::Internal,
});
let (mb, dp) = run_supervisor(
actor,
state,
ctx,
mailbox_rx,
dispatch_rx,
shutdown_rx,
system_rx,
exit_reason.clone(),
guard,
)
.await;
mailbox_rx = mb;
dispatch_rx = dp;
let reason = exit_reason.lock().unwrap().clone();
if !should_restart(&config.policy, &reason) {
break;
}
}
}));
endpoint
}
pub fn register_remote<A: Actor + 'static>(
&self,
label: &str,
wire_tx: mpsc::UnboundedSender<RemoteInvocation>,
response_registry: ResponseRegistry,
) {
self.register_remote_from_node::<A>(
label,
wire_tx,
response_registry,
&self.inner.config.node_id.clone(),
Visibility::Internal,
);
}
pub fn register_remote_from_node<A: Actor + 'static>(
&self,
label: &str,
wire_tx: mpsc::UnboundedSender<RemoteInvocation>,
response_registry: ResponseRegistry,
node_id: &str,
visibility: Visibility,
) {
let entry = ActorEntry {
label: label.to_string(),
type_id: TypeId::of::<A>(),
actor_type_name: std::any::type_name::<A>().to_string(),
location: EntryLocation::Remote {
wire_tx,
response_registry,
},
keys: Vec::new(),
node_id: node_id.to_string(),
visibility,
};
self.inner
.entries
.lock()
.unwrap()
.insert(label.to_string(), entry);
self.record_register_op(label, std::any::type_name::<A>(), &[], visibility);
instrument::actor_started(std::any::type_name::<A>());
instrument::receptionist_registration();
self.emit(ActorEvent::Registered {
label: label.to_string(),
actor_type: std::any::type_name::<A>().to_string(),
visibility,
});
}
pub fn deregister(&self, label: &str) {
self.deregister_with_reason(label, TerminationReason::Stopped);
}
pub fn deregister_with_reason(&self, label: &str, reason: TerminationReason) {
if let Some(handle) = self.inner.blip_pending.lock().unwrap().remove(label) {
handle.abort();
}
if let Some(entry) = self.inner.entries.lock().unwrap().remove(label) {
let reason_str = match &reason {
TerminationReason::Stopped => "stopped",
TerminationReason::Panicked(_) => "panicked",
TerminationReason::RestartLimitExceeded => "restart_limit_exceeded",
};
instrument::actor_stopped(&entry.actor_type_name, reason_str);
instrument::receptionist_deregistration();
self.inner.oplog.lock().unwrap().append(OpType::Remove {
label: label.to_string(),
});
if let Some(watchers) = self.inner.watches.lock().unwrap().remove(label) {
for watcher in watchers {
let terminated = ActorTerminated {
label: label.to_string(),
reason: reason.clone(),
tag: watcher.tag,
};
let _ = watcher
.system_tx
.send(SystemSignal::ActorTerminated(terminated));
}
}
if !entry.keys.is_empty() {
let mut watched_subs = self.inner.watched_listing_subscribers.lock().unwrap();
watched_subs.retain(|sub| !sub.is_closed());
for sub in watched_subs.iter() {
if sub.actor_type_id() == entry.type_id
&& entry.keys.iter().any(|k| k.id == sub.key_id())
{
sub.try_send_removed(label);
}
}
}
self.emit(ActorEvent::Deregistered {
label: entry.label,
actor_type: entry.actor_type_name,
});
}
}
pub(crate) fn add_watch(
&self,
watched_label: &str,
watcher_label: &str,
system_tx: mpsc::UnboundedSender<SystemSignal>,
) {
self.add_watch_inner(watched_label, watcher_label, system_tx, None);
}
pub(crate) fn add_watch_with_tag(
&self,
watched_label: &str,
watcher_label: &str,
system_tx: mpsc::UnboundedSender<SystemSignal>,
tag: String,
) {
self.add_watch_inner(watched_label, watcher_label, system_tx, Some(tag));
}
fn add_watch_inner(
&self,
watched_label: &str,
watcher_label: &str,
system_tx: mpsc::UnboundedSender<SystemSignal>,
tag: Option<String>,
) {
let exists = self
.inner
.entries
.lock()
.unwrap()
.contains_key(watched_label);
if !exists {
let _ = system_tx.send(SystemSignal::ActorTerminated(ActorTerminated {
label: watched_label.to_string(),
reason: TerminationReason::Stopped,
tag,
}));
return;
}
self.inner
.watches
.lock()
.unwrap()
.entry(watched_label.to_string())
.or_default()
.push(WatchEntry {
watcher_label: watcher_label.to_string(),
system_tx,
tag,
});
}
pub fn stop(&self, label: &str) {
let mut entries = self.inner.entries.lock().unwrap();
if let Some(entry) = entries.get_mut(label)
&& let EntryLocation::Local { shutdown_tx, .. } = &mut entry.location
&& let Some(tx) = shutdown_tx.take()
{
let _ = tx.send(());
}
}
pub fn lookup<A: Actor + 'static>(&self, label: &str) -> Option<Endpoint<A>> {
instrument::receptionist_lookup();
let entries = self.inner.entries.lock().unwrap();
let entry = entries.get(label)?;
if entry.type_id != TypeId::of::<A>() {
return None;
}
match &entry.location {
EntryLocation::Local {
endpoint_factory, ..
} => {
let any_endpoint = endpoint_factory.create_endpoint_any();
any_endpoint.downcast::<Endpoint<A>>().ok().map(|b| *b)
}
EntryLocation::Remote {
wire_tx,
response_registry,
} => Some(Endpoint::remote(
entry.label.clone(),
wire_tx.clone(),
response_registry.clone(),
)),
}
}
pub fn get_dispatch_sender(
&self,
label: &str,
) -> Option<mpsc::UnboundedSender<DispatchRequest>> {
let entries = self.inner.entries.lock().unwrap();
let entry = entries.get(label)?;
match &entry.location {
EntryLocation::Local { dispatch_tx, .. } => Some(dispatch_tx.clone()),
EntryLocation::Remote { .. } => None,
}
}
pub fn has_entry(&self, label: &str) -> bool {
self.inner.entries.lock().unwrap().contains_key(label)
}
pub fn subscribe_events(&self) -> mpsc::UnboundedReceiver<ActorEvent> {
let (tx, rx) = mpsc::unbounded_channel();
self.inner.event_subscribers.lock().unwrap().push(tx);
rx
}
fn emit(&self, event: ActorEvent) {
let mut subs = self.inner.event_subscribers.lock().unwrap();
subs.retain(|tx| tx.send(event.clone()).is_ok());
}
pub fn check_in<A: Actor + 'static>(&self, label: &str, key: ReceptionKey<A>) {
let erased_key = ErasedReceptionKey {
id: key.id.clone(),
type_id: TypeId::of::<A>(),
};
let entries = self.inner.entries.lock().unwrap();
let Some(entry) = entries.get(label) else {
return;
};
if entry.type_id != TypeId::of::<A>() {
return;
}
if self.inner.config.flush_interval.is_some() {
self.inner
.pending_notifications
.lock()
.unwrap()
.push(PendingListingNotification {
label: label.to_string(),
key_id: key.id.clone(),
type_id: TypeId::of::<A>(),
});
} else {
let mut listing_subs = self.inner.listing_subscribers.lock().unwrap();
listing_subs.retain(|sub| !sub.is_closed());
for sub in listing_subs.iter() {
if sub.key_id() == key.id && sub.actor_type_id() == TypeId::of::<A>() {
sub.try_send_from_entry(entry);
}
}
}
{
let mut watched_subs = self.inner.watched_listing_subscribers.lock().unwrap();
watched_subs.retain(|sub| !sub.is_closed());
for sub in watched_subs.iter() {
if sub.key_id() == key.id && sub.actor_type_id() == TypeId::of::<A>() {
sub.try_send_added(entry);
}
}
}
drop(entries);
if let Some(e) = self.inner.entries.lock().unwrap().get_mut(label) {
e.keys.push(erased_key);
}
}
pub fn listing<A: Actor + 'static>(&self, key: ReceptionKey<A>) -> Listing<A> {
let (tx, rx) = mpsc::unbounded_channel();
let entries = self.inner.entries.lock().unwrap();
let mut listing_subs = self.inner.listing_subscribers.lock().unwrap();
for entry in entries.values() {
if entry.type_id == TypeId::of::<A>()
&& entry
.keys
.iter()
.any(|k| k.id == key.id && k.type_id == TypeId::of::<A>())
{
let temp_sender = TypedListingSender::<A> {
key_id: key.id.clone(),
tx: tx.clone(),
};
temp_sender.try_send_from_entry(entry);
}
}
listing_subs.push(Box::new(TypedListingSender::<A> {
key_id: key.id.clone(),
tx,
}));
listing_subs.retain(|sub| !sub.is_closed());
Listing::new(rx)
}
pub fn watched_listing<A: Actor + 'static>(&self, key: ReceptionKey<A>) -> WatchedListing<A> {
let (tx, rx) = mpsc::unbounded_channel();
let entries = self.inner.entries.lock().unwrap();
let mut watched_subs = self.inner.watched_listing_subscribers.lock().unwrap();
for entry in entries.values() {
if entry.type_id == TypeId::of::<A>()
&& entry
.keys
.iter()
.any(|k| k.id == key.id && k.type_id == TypeId::of::<A>())
{
let temp_sender = TypedWatchedListingSender::<A> {
key_id: key.id.clone(),
tx: tx.clone(),
};
temp_sender.try_send_added(entry);
}
}
watched_subs.push(Box::new(TypedWatchedListingSender::<A> {
key_id: key.id.clone(),
tx,
}));
watched_subs.retain(|sub| !sub.is_closed());
WatchedListing::new(rx)
}
pub fn version_vector(&self) -> VersionVector {
let mut vv = self.inner.observed_versions.lock().unwrap().clone();
let oplog = self.inner.oplog.lock().unwrap();
let latest = oplog.latest_seq();
if latest > 0 {
vv.update(&self.inner.config.node_id, latest);
}
vv
}
pub fn ops_since(&self, peer_versions: &VersionVector) -> Vec<Op> {
let oplog = self.inner.oplog.lock().unwrap();
let peer_has_seen = peer_versions.get(&self.inner.config.node_id);
oplog.ops_since(peer_has_seen).to_vec()
}
pub fn public_ops_since(&self, peer_versions: &VersionVector) -> Vec<Op> {
let oplog = self.inner.oplog.lock().unwrap();
let peer_has_seen = peer_versions.get(&self.inner.config.node_id);
oplog
.ops_since(peer_has_seen)
.iter()
.filter(|op| match &op.op_type {
OpType::Register {
visibility: Visibility::Public,
..
} => true,
OpType::Register { .. } => false,
OpType::Remove { .. } => true,
})
.cloned()
.collect()
}
pub fn apply_ops(&self, ops: Vec<Op>) {
for op in ops {
{
let mut versions = self.inner.observed_versions.lock().unwrap();
let already_seen = versions.get(&op.node_id) >= op.seq;
if already_seen {
continue;
}
versions.update(&op.node_id, op.seq);
}
match &op.op_type {
OpType::Register {
label,
actor_type_name,
visibility,
..
} => {
self.emit(ActorEvent::Registered {
label: label.clone(),
actor_type: actor_type_name.clone(),
visibility: *visibility,
});
}
OpType::Remove { label } => {
self.deregister(label);
}
}
}
}
pub fn prune_node(&self, node_id: &str) -> Vec<String> {
let labels_to_remove: Vec<String> = {
let entries = self.inner.entries.lock().unwrap();
entries
.values()
.filter(|e| e.node_id == node_id)
.map(|e| e.label.clone())
.collect()
};
for label in &labels_to_remove {
self.deregister(label);
}
labels_to_remove
}
pub fn oplog_snapshot(&self) -> Vec<Op> {
self.inner.oplog.lock().unwrap().ops.clone()
}
fn record_register_op(
&self,
label: &str,
actor_type_name: &str,
key_ids: &[String],
visibility: Visibility,
) {
if visibility == Visibility::Private {
return;
}
let blip_window = self.inner.config.blip_window;
let origin_addr = self.inner.config.origin_addr.clone();
if let Some(window) = blip_window {
let receptionist = self.clone();
let label_owned = label.to_string();
let actor_type_name = actor_type_name.to_string();
let key_ids = key_ids.to_vec();
let label_for_insert = label_owned.clone();
let runtime = self.inner.runtime.clone();
let sleep_rt = runtime.clone();
let handle = runtime.spawn(Box::pin(async move {
sleep_rt.sleep(window).await;
let still_exists = receptionist
.inner
.entries
.lock()
.unwrap()
.contains_key(&label_owned);
if still_exists {
receptionist
.inner
.oplog
.lock()
.unwrap()
.append(OpType::Register {
label: label_owned.clone(),
actor_type_name,
key_ids,
origin_addr,
visibility,
});
}
receptionist
.inner
.blip_pending
.lock()
.unwrap()
.remove(&label_owned);
}));
self.inner
.blip_pending
.lock()
.unwrap()
.insert(label_for_insert, handle);
} else {
self.inner.oplog.lock().unwrap().append(OpType::Register {
label: label.to_string(),
actor_type_name: actor_type_name.to_string(),
key_ids: key_ids.to_vec(),
origin_addr,
visibility,
});
}
}
fn flush_pending_notifications(&self) {
let pending: Vec<PendingListingNotification> = {
let mut pending = self.inner.pending_notifications.lock().unwrap();
std::mem::take(&mut *pending)
};
if pending.is_empty() {
return;
}
let entries = self.inner.entries.lock().unwrap();
let mut listing_subs = self.inner.listing_subscribers.lock().unwrap();
listing_subs.retain(|sub| !sub.is_closed());
for notif in &pending {
if let Some(entry) = entries.get(¬if.label) {
for sub in listing_subs.iter() {
if sub.key_id() == notif.key_id && sub.actor_type_id() == notif.type_id {
sub.try_send_from_entry(entry);
}
}
}
}
}
}
impl Default for Receptionist {
fn default() -> Self {
Self::new()
}
}