1#![allow(clippy::new_without_default)]
13#![allow(clippy::unwrap_or_default)]
14#![deny(missing_docs)]
15#[cfg(any(unix, windows))]
18mod ipc;
19mod network;
20pub mod payload;
21pub mod process;
22pub mod remote_task;
23#[cfg(feature = "scheduler-routes")]
24pub mod route;
25pub mod safe_channel;
26
27use moirai_core::channel::{mpmc, MpmcReceiver, MpmcSender};
28const DEFAULT_MPMC_CAPACITY: usize = 1024;
30use std::{
31 collections::HashMap,
32 fmt,
33 sync::{Arc, Mutex, MutexGuard, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard},
34};
35
36pub(crate) fn lock_mutex<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
43 mutex.lock().unwrap_or_else(PoisonError::into_inner)
44}
45
46fn read_rwlock<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
48 lock.read().unwrap_or_else(PoisonError::into_inner)
49}
50
51fn write_rwlock<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
53 lock.write().unwrap_or_else(PoisonError::into_inner)
54}
55
56#[cfg(any(unix, windows))]
59pub use ipc::IpcTransport;
60pub use moirai_core::channel::{
61 ChannelError as TransportError, MpmcReceiver as Receiver, MpmcSender as Sender,
62};
63pub use network::NetworkTransport;
64#[cfg(feature = "network")]
65pub use network::TcpTransport;
66pub(crate) use network::{read_network_frame_from_stream, NETWORK_IO_TIMEOUT};
67pub use safe_channel::{
70 ArchiveSerialize, ArchiveView, ArchivedMessage, ArchivedUniversalReceiver,
71 ArchivedUniversalSender,
72};
73
74pub type TransportResult<T> = Result<T, TransportError>;
76
77#[derive(Debug, Clone, PartialEq, Eq, Hash)]
79pub enum Address {
80 Local(String),
82 Remote(RemoteAddress),
84}
85
86impl fmt::Display for Address {
87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88 match self {
89 Address::Local(id) => write!(f, "local://{}", id),
90 Address::Remote(addr) => write!(f, "{}", addr),
91 }
92 }
93}
94
95pub trait Transport: Send + Sync {
97 fn send(&self, target: &Address, data: Vec<u8>) -> TransportResult<()>;
99
100 fn recv(&self, source: &Address) -> TransportResult<Vec<u8>>;
102
103 fn supports(&self, address: &Address) -> bool;
105}
106
107type LocalChannel = (MpmcSender<Vec<u8>>, MpmcReceiver<Vec<u8>>);
109
110pub struct InMemoryTransport {
112 channels: Arc<RwLock<HashMap<String, LocalChannel>>>,
118}
119
120impl InMemoryTransport {
121 pub fn new() -> Self {
123 Self {
124 channels: Arc::new(RwLock::new(HashMap::new())),
125 }
126 }
127
128 fn get_or_create_channel(&self, id: &str) -> LocalChannel {
129 if let Some(pair) = read_rwlock(&self.channels).get(id) {
132 return pair.clone();
133 }
134 let mut channels = write_rwlock(&self.channels);
137 if let Some(pair) = channels.get(id) {
138 return pair.clone();
139 }
140 let pair = mpmc(DEFAULT_MPMC_CAPACITY);
141 channels.insert(id.to_string(), pair.clone());
142 pair
143 }
144}
145
146impl Transport for InMemoryTransport {
147 fn send(&self, target: &Address, data: Vec<u8>) -> TransportResult<()> {
148 match target {
149 Address::Local(id) => {
150 let (tx, _) = self.get_or_create_channel(id);
151 tx.send(data)
152 }
153 _ => Err(TransportError::Closed),
154 }
155 }
156
157 fn recv(&self, source: &Address) -> TransportResult<Vec<u8>> {
158 match source {
159 Address::Local(id) => {
160 let (_, rx) = self.get_or_create_channel(id);
161 rx.recv()
162 }
163 _ => Err(TransportError::Closed),
164 }
165 }
166
167 fn supports(&self, address: &Address) -> bool {
168 matches!(address, Address::Local(_))
169 }
170}
171
172pub struct TransportManager {
174 transports: Vec<Box<dyn Transport>>,
175}
176
177impl TransportManager {
178 pub fn new() -> Self {
181 Self {
182 transports: vec![
183 Box::new(InMemoryTransport::new()),
184 Box::new(NetworkTransport {}),
185 ],
186 }
187 }
188
189 pub fn send(&self, target: &Address, data: Vec<u8>) -> TransportResult<()> {
195 for transport in &self.transports {
196 if transport.supports(target) {
197 return transport.send(target, data);
198 }
199 }
200 Err(TransportError::Closed)
201 }
202
203 pub fn recv(&self, source: &Address) -> TransportResult<Vec<u8>> {
209 for transport in &self.transports {
210 if transport.supports(source) {
211 return transport.recv(source);
212 }
213 }
214 Err(TransportError::Closed)
215 }
216}
217
218#[derive(Debug, Clone, PartialEq, Eq, Hash)]
230pub struct RemoteAddress {
231 pub host: String,
233 pub port: u16,
235 pub service: String,
237}
238
239impl fmt::Display for RemoteAddress {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 write!(f, "{}://{}:{}", self.service, self.host, self.port)
242 }
243}
244
245pub struct MessageRouter<T: Transport> {
255 transport: Arc<T>,
256 subscriptions: Mutex<HashMap<String, Vec<Address>>>,
257}
258
259impl<T: Transport> MessageRouter<T> {
260 pub fn new(transport: Arc<T>) -> Self {
262 Self {
263 transport,
264 subscriptions: Mutex::new(HashMap::new()),
265 }
266 }
267
268 pub fn subscribe(&self, topic: &str, address: Address) {
271 let mut subs = lock_mutex(&self.subscriptions);
272 let entry = subs.entry(topic.to_string()).or_default();
273 if !entry.contains(&address) {
274 entry.push(address);
275 }
276 }
277
278 pub fn unsubscribe(&self, topic: &str, address: &Address) -> bool {
281 let mut subs = lock_mutex(&self.subscriptions);
282 if let Some(entry) = subs.get_mut(topic) {
283 let before = entry.len();
284 entry.retain(|a| a != address);
285 let removed = entry.len() != before;
286 if entry.is_empty() {
287 subs.remove(topic);
288 }
289 return removed;
290 }
291 false
292 }
293
294 pub fn publish(&self, topic: &str, data: Vec<u8>) -> TransportResult<usize> {
303 let targets: Vec<Address> = {
306 let subs = lock_mutex(&self.subscriptions);
307 match subs.get(topic) {
308 Some(addresses) => addresses.clone(),
309 None => return Ok(0),
310 }
311 };
312
313 let mut delivered = 0;
318 let Some(last) = targets.len().checked_sub(1) else {
319 return Ok(0);
320 };
321 let mut data = Some(data);
322 for (index, addr) in targets.iter().enumerate() {
323 let payload = if index == last {
324 data.take()
325 .expect("invariant: original buffer moved exactly once, at the last subscriber")
326 } else {
327 data.as_ref()
328 .expect("invariant: original buffer present until the last subscriber")
329 .clone()
330 };
331 self.transport.send(addr, payload)?;
332 delivered += 1;
333 }
334 Ok(delivered)
335 }
336
337 pub fn subscriber_count(&self, topic: &str) -> usize {
339 lock_mutex(&self.subscriptions)
340 .get(topic)
341 .map_or(0, Vec::len)
342 }
343}
344
345pub struct ConnectionManager {
347 connections: Arc<Mutex<HashMap<Address, ConnectionState>>>,
348}
349
350#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum ConnectionState {
353 Connected,
355 Disconnected,
357}
358
359impl ConnectionManager {
360 pub fn new() -> Self {
362 Self {
363 connections: Arc::new(Mutex::new(HashMap::new())),
364 }
365 }
366
367 pub fn connect(&self, address: &Address) -> TransportResult<()> {
369 let mut conns = lock_mutex(&self.connections);
370 conns.insert(address.clone(), ConnectionState::Connected);
371 Ok(())
372 }
373
374 pub fn disconnect(&self, address: &Address) -> TransportResult<()> {
376 let mut conns = lock_mutex(&self.connections);
377 conns.insert(address.clone(), ConnectionState::Disconnected);
378 Ok(())
379 }
380
381 #[must_use]
383 pub fn state(&self, address: &Address) -> Option<ConnectionState> {
384 lock_mutex(&self.connections).get(address).copied()
385 }
386
387 #[must_use]
389 pub fn is_connected(&self, address: &Address) -> bool {
390 self.state(address) == Some(ConnectionState::Connected)
391 }
392
393 #[must_use]
395 pub fn connected_addresses(&self) -> Vec<Address> {
396 lock_mutex(&self.connections)
397 .iter()
398 .filter(|(_, state)| **state == ConnectionState::Connected)
399 .map(|(addr, _)| addr.clone())
400 .collect()
401 }
402}
403
404#[cfg(test)]
405mod tests {
406 use super::*;
407
408 #[test]
409 fn test_channel_compatibility() {
410 let (tx, rx) = moirai_core::channel::mpmc::<i32>(10);
411
412 assert!(tx.send(42).is_ok());
413 assert_eq!(rx.recv().unwrap(), 42);
414 }
415
416 #[test]
417 fn test_in_memory_transport() {
418 let transport1 = InMemoryTransport::new();
419 let transport2 = InMemoryTransport::new();
420
421 assert!(transport1
425 .send(&Address::Local("t1".to_string()), vec![1])
426 .is_ok());
427 assert_eq!(
428 transport1.recv(&Address::Local("t1".to_string())).unwrap(),
429 vec![1]
430 );
431
432 assert!(transport2
433 .send(&Address::Local("t2".to_string()), vec![2])
434 .is_ok());
435 assert_eq!(
436 transport2.recv(&Address::Local("t2".to_string())).unwrap(),
437 vec![2]
438 );
439 }
440
441 #[test]
442 fn network_transport_transfers_length_prefixed_remote_bytes() {
443 let transport = NetworkTransport {};
444 let address = loopback_remote_address();
445 let payload = b"server route payload".to_vec();
446 let expected = payload.clone();
447 let receiver_address = Address::Remote(address.clone());
448 let receiver = std::thread::spawn(move || transport.recv(&receiver_address).unwrap());
449
450 std::thread::sleep(std::time::Duration::from_millis(10));
451 NetworkTransport {}
452 .send(&Address::Remote(address), payload)
453 .unwrap();
454
455 assert_eq!(receiver.join().unwrap(), expected);
456 }
457
458 #[test]
459 fn transport_manager_routes_remote_bytes_through_network_transport() {
460 let manager = TransportManager::new();
461 let address = loopback_remote_address();
462 let payload = b"transport manager remote payload".to_vec();
463 let expected = payload.clone();
464 let receiver_address = Address::Remote(address.clone());
465 let receiver =
466 std::thread::spawn(move || TransportManager::new().recv(&receiver_address).unwrap());
467
468 std::thread::sleep(std::time::Duration::from_millis(10));
469 manager.send(&Address::Remote(address), payload).unwrap();
470
471 assert_eq!(receiver.join().unwrap(), expected);
472 }
473
474 fn loopback_remote_address() -> RemoteAddress {
475 let listener = std::net::TcpListener::bind("127.0.0.1:0").unwrap();
476 let port = listener.local_addr().unwrap().port();
477 drop(listener);
478 RemoteAddress {
479 host: "127.0.0.1".to_string(),
480 port,
481 service: "moirai-test".to_string(),
482 }
483 }
484
485 #[test]
486 fn message_router_delivers_to_each_subscriber_once() {
487 let transport = Arc::new(InMemoryTransport::new());
488 let router = MessageRouter::new(Arc::clone(&transport));
489 let sub_a = Address::Local("sub_a".to_string());
490 let sub_b = Address::Local("sub_b".to_string());
491
492 router.subscribe("topic", sub_a.clone());
493 router.subscribe("topic", sub_b.clone());
494 router.subscribe("topic", sub_a.clone()); assert_eq!(router.subscriber_count("topic"), 2);
496
497 let delivered = router.publish("topic", vec![1, 2, 3]).unwrap();
498 assert_eq!(delivered, 2);
499
500 assert_eq!(transport.recv(&sub_a).unwrap(), vec![1, 2, 3]);
503 assert_eq!(transport.recv(&sub_b).unwrap(), vec![1, 2, 3]);
504 }
505
506 #[test]
507 fn message_router_single_subscriber_receives_moved_buffer() {
508 let transport = Arc::new(InMemoryTransport::new());
511 let router = MessageRouter::new(Arc::clone(&transport));
512 let sub = Address::Local("solo".to_string());
513 router.subscribe("t", sub.clone());
514
515 let delivered = router.publish("t", vec![7, 8, 9]).unwrap();
516 assert_eq!(delivered, 1);
517 assert_eq!(transport.recv(&sub).unwrap(), vec![7, 8, 9]);
518 }
519
520 #[test]
521 fn message_router_unknown_topic_delivers_nothing() {
522 let transport = Arc::new(InMemoryTransport::new());
523 let router = MessageRouter::new(transport);
524 assert_eq!(router.publish("absent", vec![0]).unwrap(), 0);
525 }
526
527 #[test]
528 fn message_router_unsubscribe_stops_delivery() {
529 let transport = Arc::new(InMemoryTransport::new());
530 let router = MessageRouter::new(Arc::clone(&transport));
531 let sub = Address::Local("s".to_string());
532
533 router.subscribe("t", sub.clone());
534 assert!(router.unsubscribe("t", &sub));
535 assert!(
536 !router.unsubscribe("t", &sub),
537 "second unsubscribe is a no-op"
538 );
539 assert_eq!(router.subscriber_count("t"), 0);
540 assert_eq!(router.publish("t", vec![9]).unwrap(), 0);
541 }
542
543 #[test]
544 fn connection_manager_tracks_and_reports_state() {
545 let mgr = ConnectionManager::new();
546 let addr = Address::Local("node".to_string());
547
548 assert_eq!(mgr.state(&addr), None);
549 assert!(!mgr.is_connected(&addr));
550 assert!(mgr.connected_addresses().is_empty());
551
552 mgr.connect(&addr).unwrap();
553 assert!(mgr.is_connected(&addr));
554 assert_eq!(mgr.state(&addr), Some(ConnectionState::Connected));
555 assert_eq!(mgr.connected_addresses(), vec![addr.clone()]);
556
557 mgr.disconnect(&addr).unwrap();
558 assert!(!mgr.is_connected(&addr));
559 assert_eq!(mgr.state(&addr), Some(ConnectionState::Disconnected));
560 assert!(mgr.connected_addresses().is_empty());
561 }
562}