Skip to main content

acktor_ipc/
node.rs

1//! Node actor for managing IPC connections and sessions.
2//!
3
4use std::marker::PhantomData;
5use std::sync::Arc;
6
7use ahash::{HashMap, HashSet};
8use dashmap::DashMap;
9use futures_util::future::join_all;
10use tracing::{error, info, warn};
11
12use acktor::{
13    Actor, ActorContext, ActorId, Address, ErrorReport, Handler, JoinHandle, Recipient, Signal,
14    message::FutureMessageResult,
15    observer::{ObserverSet, SubjectActor},
16    supervisor::SupervisionEvent,
17    utils::{debug_trace, terminate_actor},
18};
19
20use crate::double_map::DoubleMap;
21use crate::errors::NodeError;
22use crate::ipc_method::{IpcConnection, IpcListener};
23use crate::remote_actor::{
24    RemoteActor, RemoteActorFactory, RemoteActorFactoryRegistry, RemoteActorRegistry,
25    RemoteActorShim,
26};
27use crate::session::{self, Session, SessionHandle};
28
29pub mod command;
30
31mod event;
32pub use event::NodeEvent;
33
34mod context;
35use context::NodeContext;
36
37pub(crate) mod factory;
38use factory::Factory;
39
40type Result<T> = std::result::Result<T, NodeError>;
41
42pub(crate) type LabelMap = Arc<DashMap<String, ActorId, ahash::RandomState>>;
43
44/// An actor which helps to manage the IPC connections.
45///
46/// The node can hold multiple [`IpcListener`]s to accept incoming IPC connections on several
47/// endpoints in parallel. Outbound connections are initiated by sending a
48/// [`Connect<C>`][command::Connect] command.
49#[derive(Default)]
50pub struct Node {
51    listener_labels: HashSet<String>,
52    listeners: Vec<Box<dyn IpcListener>>,
53    // registered factories for peer-initiated actor creation, keyed by the type name.
54    factory_registry: Option<RemoteActorFactoryRegistry>,
55    factory: Option<Address<Factory>>,
56    // `registry` and `label_map`  are cloned into the factory and every session, so all holders
57    // observe the same contents. They are wrapped in `Arc` so clone is cheap.
58    registry: RemoteActorRegistry,
59    label_map: LabelMap,
60    sessions: DoubleMap<ActorId, String, Address<Session>>,
61    children: HashMap<Recipient<Signal>, JoinHandle<()>>,
62    observers: ObserverSet<NodeEvent>,
63}
64
65impl Node {
66    /// Constructs a new [`Node`].
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Adds an IPC listener to the node.
72    ///
73    /// If the node already has a listener listening on the same endpoint, the new listener will
74    /// replace the existing one. Note this is not the same as the
75    /// [`AddListener`][command::AddListener] command, which will not replace the existing
76    /// listener.
77    pub fn with_listener<L>(mut self, listener: L) -> Self
78    where
79        L: IpcListener,
80    {
81        if self.listener_labels.contains(listener.local_endpoint()) {
82            self.listeners
83                .retain(|l| l.local_endpoint() != listener.local_endpoint());
84        } else {
85            self.listener_labels
86                .insert(listener.local_endpoint().to_string());
87        }
88        self.listeners.push(Box::new(listener));
89        self
90    }
91
92    /// Adds an actor which implements [`RemoteActor`] trait to the node.
93    pub fn with_actor<A>(self, actor: Address<A>) -> Self
94    where
95        A: RemoteActor,
96    {
97        self.registry.insert(actor);
98        self
99    }
100
101    /// Adds an actor factory so that remote peers can create instances of `A` by sending a
102    /// `CreateActor` node command to this node.
103    pub fn with_actor_factory<A>(mut self) -> Self
104    where
105        A: RemoteActorFactory,
106    {
107        self.factory_registry.get_or_insert_default().insert(
108            A::TYPE_NAME.to_string(),
109            Arc::new(RemoteActorShim::<A>(PhantomData)),
110        );
111        self
112    }
113
114    async fn create_session(
115        &mut self,
116        connection: Box<dyn IpcConnection>,
117        session_label: Option<String>,
118        ctx: &mut <Self as Actor>::Context,
119    ) -> Result<Address<Session>> {
120        let endpoint = connection.peer_endpoint().to_string();
121
122        let session_label = session_label.unwrap_or_else(|| endpoint.clone());
123
124        if self.sessions.contains_key2(&session_label) {
125            return Err(NodeError::CreateSessionFailed(
126                format!("session with label '{}' already exists", session_label).into(),
127            ));
128        }
129
130        let (address, join_handle) = Session::create(endpoint.clone(), |child_ctx| {
131            child_ctx.set_supervisor(Some(ctx.address().into()));
132
133            // SAFETY: `self.factory` is assigned at the end of `post_start`, and `create_session`
134            // is only reachable from message handlers, which the actor runtime does not dispatch
135            // until `post_start` has returned `Ok`. The unwrap is therefore infallible.
136            Ok(Session::new(
137                connection,
138                self.factory.clone().unwrap(),
139                self.registry.clone(),
140                self.label_map.clone(),
141            ))
142        })
143        .map_err(|e| NodeError::CreateSessionFailed(e.into()))?;
144
145        let session_id = address.index();
146
147        // this will never fail since we have verified the session label is unique and the actor id
148        // is also unique in the same process
149        let _ = self
150            .sessions
151            .insert(session_id, session_label.clone(), address.clone());
152
153        self.children.insert(address.clone().into(), join_handle);
154
155        self.notify_observers(NodeEvent::SessionCreated(address.clone(), session_label))
156            .await;
157
158        Ok(address)
159    }
160
161    fn get_session(&self, session_ref: &SessionHandle) -> Result<Address<Session>> {
162        match session_ref {
163            SessionHandle::Address(address) => self
164                .sessions
165                .get_by_key1(&address.index())
166                .cloned()
167                .ok_or_else(|| NodeError::SessionNotFound(address.index().to_string())),
168            SessionHandle::Index(index) => self
169                .sessions
170                .get_by_key1(index)
171                .cloned()
172                .ok_or_else(|| NodeError::SessionNotFound(index.to_string())),
173            SessionHandle::Label(label) => self
174                .sessions
175                .get_by_key2(label)
176                .cloned()
177                .ok_or_else(|| NodeError::SessionNotFound(label.clone())),
178        }
179    }
180}
181
182impl Actor for Node {
183    type Context = NodeContext;
184    type Error = NodeError;
185
186    async fn post_start(&mut self, _ctx: &mut Self::Context) -> Result<()> {
187        let factory_registry = self.factory_registry.take().unwrap_or_default();
188
189        // the factory actor never fail, so it is not supervised
190        let (address, join_handle) = Factory::new(
191            factory_registry,
192            self.registry.clone(),
193            self.label_map.clone(),
194        )
195        .run("factory")?;
196
197        self.children.insert(address.clone().into(), join_handle);
198        self.factory = Some(address);
199
200        info!("Node is ready");
201
202        Ok(())
203    }
204
205    async fn post_stop(&mut self, _ctx: &mut Self::Context) -> Result<()> {
206        join_all(
207            self.children
208                .drain()
209                .map(|(address, join_handle)| terminate_actor(address, join_handle)),
210        )
211        .await;
212
213        info!("Node is stopped");
214
215        Ok(())
216    }
217}
218
219impl SubjectActor<NodeEvent> for Node {
220    fn observers_mut(&mut self) -> &mut ObserverSet<NodeEvent> {
221        &mut self.observers
222    }
223}
224
225impl<L> Handler<command::AddListener<L>> for Node
226where
227    L: IpcListener,
228{
229    type Result = bool;
230
231    async fn handle(
232        &mut self,
233        msg: command::AddListener<L>,
234        _ctx: &mut Self::Context,
235    ) -> Self::Result {
236        debug_trace!("Handle command {:?}", msg,);
237
238        let label = msg.0.local_endpoint();
239        if self.listener_labels.contains(label) {
240            false
241        } else {
242            self.listener_labels.insert(label.to_string());
243            self.listeners.push(Box::new(msg.0));
244            true
245        }
246    }
247}
248
249impl Handler<command::RemoveListener> for Node {
250    type Result = bool;
251
252    async fn handle(
253        &mut self,
254        msg: command::RemoveListener,
255        _ctx: &mut Self::Context,
256    ) -> Self::Result {
257        debug_trace!("Handle command {:?}", msg);
258
259        let label = msg.0;
260        self.listener_labels.remove(&label)
261    }
262}
263
264impl<A> Handler<command::AddActor<A>> for Node
265where
266    A: RemoteActor,
267{
268    type Result = bool;
269
270    async fn handle(
271        &mut self,
272        msg: command::AddActor<A>,
273        _ctx: &mut Self::Context,
274    ) -> Self::Result {
275        debug_trace!("Handle command {:?}", msg,);
276
277        self.registry.insert(msg.0)
278    }
279}
280
281impl Handler<command::RemoveActor> for Node {
282    type Result = bool;
283
284    async fn handle(
285        &mut self,
286        msg: command::RemoveActor,
287        _ctx: &mut Self::Context,
288    ) -> Self::Result {
289        debug_trace!("Handle command {:?}", msg);
290
291        let actor_id = msg.0;
292        self.label_map.retain(|_, id| *id != actor_id);
293        self.registry.remove(actor_id).is_some()
294    }
295}
296
297impl<T> Handler<command::Connect<T>> for Node
298where
299    T: IpcConnection,
300{
301    type Result = Result<Address<Session>>;
302
303    async fn handle(&mut self, msg: command::Connect<T>, ctx: &mut Self::Context) -> Self::Result {
304        debug_trace!("Handle command {:?}", msg);
305
306        let command::Connect {
307            endpoint,
308            session_label,
309            ..
310        } = msg;
311
312        let connection = T::connect(&endpoint).await?;
313        let connection: Box<dyn IpcConnection> = Box::new(connection);
314        let address = self.create_session(connection, session_label, ctx).await?;
315
316        Ok(address)
317    }
318}
319
320impl Handler<command::CreateRemoteActor> for Node {
321    type Result = FutureMessageResult<command::CreateRemoteActor>;
322
323    async fn handle(
324        &mut self,
325        msg: command::CreateRemoteActor,
326        _ctx: &mut Self::Context,
327    ) -> Self::Result {
328        debug_trace!("Handle command {:?}", msg);
329
330        let command::CreateRemoteActor {
331            session,
332            label,
333            r#type,
334            config,
335        } = msg;
336
337        let session = self.get_session(&session);
338
339        FutureMessageResult::new(async move {
340            session?
341                .send(session::command::CreateRemoteActor {
342                    label,
343                    r#type,
344                    config,
345                })
346                .await?
347                // this await is time consuming since it involves IPC
348                .await?
349                .map_err(NodeError::CreateRemoteActorFailed)
350        })
351    }
352}
353
354impl Handler<command::GetRemoteActor> for Node {
355    type Result = FutureMessageResult<command::GetRemoteActor>;
356
357    async fn handle(
358        &mut self,
359        msg: command::GetRemoteActor,
360        _ctx: &mut Self::Context,
361    ) -> Self::Result {
362        debug_trace!("Handle command {:?}", msg);
363
364        let command::GetRemoteActor { session, actor } = msg;
365
366        let session = self.get_session(&session);
367
368        FutureMessageResult::new(async move {
369            session?
370                .send(session::command::GetRemoteActor { actor })
371                .await?
372                // this await is time consuming since it involves IPC
373                .await?
374                .map_err(NodeError::RemoteActorNotFound)
375        })
376    }
377}
378
379impl Handler<SupervisionEvent<Session>> for Node {
380    type Result = ();
381
382    async fn handle(
383        &mut self,
384        msg: SupervisionEvent<Session>,
385        _ctx: &mut Self::Context,
386    ) -> Self::Result {
387        debug_trace!("Handle supervision event {:?}", msg);
388
389        match msg {
390            SupervisionEvent::Warn(actor, e) => {
391                warn!("Session {} error: {}", actor.index(), e.report());
392            }
393            SupervisionEvent::Terminated(session, e) => {
394                if let Some(e) = e {
395                    error!(
396                        "Session {} is stopped with error: {}",
397                        session.index(),
398                        e.report()
399                    );
400                }
401
402                self.sessions.retain(|_, _, v| v != &session);
403                self.children.remove(&session.clone().into());
404
405                self.notify_observers(NodeEvent::SessionDeleted(session))
406                    .await;
407            }
408            _ => {}
409        }
410    }
411}