Skip to main content

autoagents_core/runtime/
single_threaded.rs

1use super::{Runtime, RuntimeError};
2use crate::agent::constants::DEFAULT_CHANNEL_BUFFER;
3use crate::utils::{BoxEventStream, receiver_into_stream};
4use crate::{
5    actor::{AnyActor, Transport},
6    error::Error,
7};
8use async_trait::async_trait;
9use autoagents_protocol::{Event, InternalEvent, RuntimeID};
10use futures_util::StreamExt;
11use log::{debug, error, info, warn};
12use std::{
13    any::{Any, TypeId},
14    collections::HashMap,
15    sync::{
16        Arc,
17        atomic::{AtomicBool, Ordering},
18    },
19};
20use tokio::sync::{Mutex, Notify, RwLock, broadcast, mpsc};
21use tokio_stream::wrappers::{BroadcastStream, errors::BroadcastStreamRecvError};
22use uuid::Uuid;
23
24const DEFAULT_INTERNAL_BUFFER: usize = 1000;
25
26/// Topic subscription entry storing type information and actor references
27#[derive(Debug)]
28struct Subscription {
29    topic_type: TypeId,
30    actors: Vec<Arc<dyn AnyActor>>,
31}
32
33#[derive(Debug)]
34/// Single-threaded runtime implementation with internal event routing
35pub struct SingleThreadedRuntime {
36    pub id: RuntimeID,
37    // External event channel for application consumption
38    external_tx: mpsc::Sender<Event>,
39    external_rx: Mutex<Option<mpsc::Receiver<Event>>>,
40    // Broadcast event channel for multi-subscriber consumption
41    broadcast_tx: broadcast::Sender<Event>,
42    // Internal event channel for runtime processing
43    internal_tx: mpsc::Sender<InternalEvent>,
44    internal_rx: Mutex<Option<mpsc::Receiver<InternalEvent>>>,
45    // Subscriptions map: topic_name -> Subscription
46    subscriptions: Arc<RwLock<HashMap<String, Subscription>>>,
47    // Transport layer for message delivery
48    transport: Arc<dyn Transport>,
49    // Runtime state
50    shutdown_flag: Arc<AtomicBool>,
51    shutdown_notify: Arc<Notify>,
52}
53
54impl SingleThreadedRuntime {
55    pub fn new(channel_buffer: Option<usize>) -> Arc<Self> {
56        Self::with_transport(channel_buffer, Arc::new(crate::actor::LocalTransport))
57    }
58
59    pub fn with_transport(
60        channel_buffer: Option<usize>,
61        transport: Arc<dyn Transport>,
62    ) -> Arc<Self> {
63        let id = Uuid::new_v4();
64        let buffer_size = channel_buffer.unwrap_or(DEFAULT_CHANNEL_BUFFER);
65
66        // Create channels
67        let (external_tx, external_rx) = mpsc::channel(buffer_size);
68        let (internal_tx, internal_rx) = mpsc::channel(DEFAULT_INTERNAL_BUFFER);
69        let (broadcast_tx, _) = broadcast::channel(buffer_size);
70
71        Arc::new(Self {
72            id,
73            external_tx,
74            external_rx: Mutex::new(Some(external_rx)),
75            broadcast_tx,
76            internal_tx,
77            internal_rx: Mutex::new(Some(internal_rx)),
78            subscriptions: Arc::new(RwLock::new(HashMap::new())),
79            transport,
80            shutdown_flag: Arc::new(AtomicBool::new(false)),
81            shutdown_notify: Arc::new(Notify::new()),
82        })
83    }
84
85    /// Process internal events in the runtime
86    async fn process_internal_event(&self, event: InternalEvent) -> Result<(), Error> {
87        debug!("Received internal event: {event:?}");
88        match event {
89            InternalEvent::ProtocolEvent(event) => {
90                self.process_protocol_event(*event).await?;
91            }
92            InternalEvent::Shutdown => {
93                self.shutdown_flag.store(true, Ordering::SeqCst);
94                self.shutdown_notify.notify_waiters();
95            }
96        }
97        Ok(())
98    }
99
100    /// Forward protocol events to external channel
101    async fn process_protocol_event(&self, event: Event) -> Result<(), Error> {
102        if let Event::PublishMessage {
103            topic_type,
104            topic_name,
105            message,
106        } = event
107        {
108            self.handle_publish_message(&topic_name, topic_type, message)
109                .await?;
110        } else {
111            //Other protocol events are sent to external
112            let _ = self.broadcast_tx.send(event.clone());
113            self.external_tx
114                .send(event)
115                .await
116                .map_err(|e| RuntimeError::EventError(Box::new(e)))?;
117        }
118        Ok(())
119    }
120
121    /// Handle message publishing to topic subscribers
122    async fn handle_publish_message(
123        &self,
124        topic_name: &str,
125        topic_type: TypeId,
126        message: Arc<dyn Any + Send + Sync>,
127    ) -> Result<(), RuntimeError> {
128        debug!("Handling publish event: {topic_name}");
129
130        let subscriptions = self.subscriptions.read().await;
131
132        if let Some(subscription) = subscriptions.get(topic_name) {
133            // Verify type safety
134            if subscription.topic_type != topic_type {
135                error!(
136                    "Type mismatch for topic '{}': expected {:?}, got {:?}",
137                    topic_name, subscription.topic_type, topic_type
138                );
139                return Err(RuntimeError::TopicTypeMismatch(
140                    topic_name.to_owned(),
141                    topic_type,
142                ));
143            }
144
145            // Send to all subscribed actors sequentially to maintain strict ordering
146            for actor in &subscription.actors {
147                if let Err(e) = self
148                    .transport
149                    .send(actor.as_ref(), Arc::clone(&message))
150                    .await
151                {
152                    error!("Failed to send message to subscriber: {e}");
153                }
154            }
155        } else {
156            debug!("No subscribers for topic: {topic_name}");
157        }
158
159        Ok(())
160    }
161
162    /// Handle actor subscription to a topic
163    async fn handle_subscribe(
164        &self,
165        topic_name: &str,
166        topic_type: TypeId,
167        actor: Arc<dyn AnyActor>,
168    ) -> Result<(), RuntimeError> {
169        info!("Actor subscribing to topic: {topic_name}");
170
171        let mut subscriptions = self.subscriptions.write().await;
172
173        match subscriptions.get_mut(topic_name) {
174            Some(subscription) => {
175                // Verify type consistency
176                if subscription.topic_type != topic_type {
177                    return Err(RuntimeError::TopicTypeMismatch(
178                        topic_name.to_string(),
179                        subscription.topic_type,
180                    ));
181                }
182                subscription.actors.push(actor);
183            }
184            None => {
185                // Create new subscription
186                subscriptions.insert(
187                    topic_name.to_string(),
188                    Subscription {
189                        topic_type,
190                        actors: vec![actor],
191                    },
192                );
193            }
194        }
195
196        Ok(())
197    }
198
199    /// Start the internal event processing loop
200    async fn event_loop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
201        let mut internal_rx = self
202            .internal_rx
203            .lock()
204            .await
205            .take()
206            .ok_or("Internal receiver already taken")?;
207
208        info!("Runtime event loop starting");
209
210        loop {
211            tokio::select! {
212                // Process internal events
213                Some(event) = internal_rx.recv() => {
214                    debug!("Processing internal event");
215
216                    // Check for shutdown event first
217                    if matches!(event, InternalEvent::Shutdown) {
218                        info!("Received shutdown event");
219                        self.process_internal_event(event).await?;
220                        break;
221                    }
222
223                    if let Err(e) = self.process_internal_event(event).await {
224                        error!("Error processing internal event: {e}");
225                        break;
226                    }
227                }
228                // Check for shutdown notification
229                _ = self.shutdown_notify.notified() => {
230                    if self.shutdown_flag.load(Ordering::SeqCst) {
231                        info!("Runtime received shutdown notification");
232                        break;
233                    }
234                }
235                // Handle channel closure
236                else => {
237                    warn!("Internal event channel closed");
238                    break;
239                }
240            }
241        }
242
243        // Drain remaining events
244        info!("Draining remaining events before shutdown");
245        while let Ok(event) = internal_rx.try_recv() {
246            if let Err(e) = self.process_internal_event(event).await {
247                error!("Error processing event during shutdown: {e}");
248            }
249        }
250
251        info!("Runtime event loop stopped");
252        Ok(())
253    }
254}
255
256#[async_trait]
257impl Runtime for SingleThreadedRuntime {
258    fn id(&self) -> RuntimeID {
259        self.id
260    }
261
262    async fn subscribe_any(
263        &self,
264        topic_name: &str,
265        topic_type: TypeId,
266        actor: Arc<dyn AnyActor>,
267    ) -> Result<(), RuntimeError> {
268        self.handle_subscribe(topic_name, topic_type, actor).await
269    }
270
271    async fn publish_any(
272        &self,
273        topic_name: &str,
274        topic_type: TypeId,
275        message: Arc<dyn Any + Send + Sync>,
276    ) -> Result<(), RuntimeError> {
277        self.handle_publish_message(topic_name, topic_type, message)
278            .await
279    }
280
281    fn tx(&self) -> mpsc::Sender<Event> {
282        // Create an intercepting sender that routes events through internal processing
283        let internal_tx = self.internal_tx.clone();
284        let (interceptor_tx, mut interceptor_rx) = mpsc::channel::<Event>(DEFAULT_CHANNEL_BUFFER);
285
286        tokio::spawn(async move {
287            while let Some(event) = interceptor_rx.recv().await {
288                if let Err(e) = internal_tx
289                    .send(InternalEvent::ProtocolEvent(Box::new(event)))
290                    .await
291                {
292                    error!("Failed to forward event to internal channel: {e}");
293                    break;
294                }
295            }
296        });
297
298        interceptor_tx
299    }
300
301    async fn transport(&self) -> Arc<dyn Transport> {
302        Arc::clone(&self.transport)
303    }
304
305    async fn take_event_receiver(&self) -> Option<BoxEventStream<Event>> {
306        let mut guard = self.external_rx.lock().await;
307        guard.take().map(receiver_into_stream)
308    }
309
310    async fn subscribe_events(&self) -> BoxEventStream<Event> {
311        let rx = self.broadcast_tx.subscribe();
312        let stream = BroadcastStream::new(rx)
313            .filter_map(|item: Result<Event, BroadcastStreamRecvError>| async move { item.ok() });
314        Box::pin(stream)
315    }
316
317    async fn run(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
318        info!("Starting SingleThreadedRuntime {}", self.id);
319        self.event_loop().await
320    }
321
322    async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
323        if self.shutdown_flag.load(Ordering::SeqCst) {
324            return Ok(());
325        }
326
327        info!("Initiating runtime shutdown for {}", self.id);
328
329        // Send shutdown signal
330        if let Err(e) = self.internal_tx.send(InternalEvent::Shutdown).await {
331            if self.shutdown_flag.load(Ordering::SeqCst) {
332                return Ok(());
333            }
334            return Err(format!("Failed to send shutdown signal: {e}").into());
335        }
336
337        // Wait a brief moment for shutdown to complete
338        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
339
340        Ok(())
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use crate::actor::{CloneableMessage, Topic};
348    use crate::runtime::{RuntimeConfig, TypedRuntime};
349    use ractor::{Actor, ActorProcessingErr, ActorRef};
350    use tokio::time::{Duration, sleep};
351
352    // Test message types
353    #[derive(Clone, Debug)]
354    struct TestMessage {
355        content: String,
356    }
357
358    impl crate::actor::ActorMessage for TestMessage {}
359    impl CloneableMessage for TestMessage {}
360
361    // Test actor
362    struct TestActor {
363        received: Arc<Mutex<Vec<String>>>,
364    }
365
366    #[async_trait]
367    impl Actor for TestActor {
368        type Msg = TestMessage;
369        type State = ();
370        type Arguments = Arc<Mutex<Vec<String>>>;
371
372        async fn pre_start(
373            &self,
374            _myself: ActorRef<Self::Msg>,
375            _args: Self::Arguments,
376        ) -> Result<Self::State, ActorProcessingErr> {
377            Ok(())
378        }
379
380        async fn handle(
381            &self,
382            _myself: ActorRef<Self::Msg>,
383            message: Self::Msg,
384            _state: &mut Self::State,
385        ) -> Result<(), ActorProcessingErr> {
386            let mut received = self.received.lock().await;
387            received.push(message.content);
388            Ok(())
389        }
390    }
391
392    #[tokio::test]
393    async fn test_runtime_creation() {
394        let runtime = SingleThreadedRuntime::new(None);
395        assert_ne!(runtime.id(), Uuid::nil());
396    }
397
398    #[tokio::test]
399    async fn test_publish_subscribe_cloneable() {
400        let runtime = SingleThreadedRuntime::new(Some(10));
401        let runtime_handle = runtime.clone();
402
403        // Start runtime in background
404        let runtime_task = tokio::spawn(async move { runtime_handle.run().await });
405
406        // Create test actor
407        let received = Arc::new(Mutex::new(Vec::new()));
408        let (actor_ref, _actor_handle) = Actor::spawn(
409            None,
410            TestActor {
411                received: received.clone(),
412            },
413            received.clone(),
414        )
415        .await
416        .unwrap();
417
418        // Subscribe to topic
419        let topic = Topic::<TestMessage>::new("test_topic");
420        runtime.subscribe(&topic, actor_ref).await.unwrap();
421
422        // Publish messages
423        runtime
424            .publish(
425                &topic,
426                TestMessage {
427                    content: "Hello".to_string(),
428                },
429            )
430            .await
431            .unwrap();
432
433        runtime
434            .publish(
435                &topic,
436                TestMessage {
437                    content: "World".to_string(),
438                },
439            )
440            .await
441            .unwrap();
442
443        // Wait for messages to be processed
444        sleep(Duration::from_millis(100)).await;
445
446        // Verify messages were received
447        let received_msgs = received.lock().await;
448        assert_eq!(received_msgs.len(), 2);
449        assert_eq!(received_msgs[0], "Hello");
450        assert_eq!(received_msgs[1], "World");
451
452        // Shutdown
453        runtime.stop().await.unwrap();
454        runtime_task.abort();
455    }
456
457    #[tokio::test]
458    async fn test_type_safety() {
459        let runtime = SingleThreadedRuntime::new(None);
460        let runtime_handle = runtime.clone();
461
462        // Start runtime in background
463        let runtime_task = tokio::spawn(async move { runtime_handle.run().await });
464
465        // Create topic and subscribe with one type
466        let topic_name = "typed_topic";
467        let topic1 = Topic::<TestMessage>::new(topic_name);
468
469        let received = Arc::new(Mutex::new(Vec::new()));
470        let (actor_ref, _) = Actor::spawn(
471            None,
472            TestActor {
473                received: received.clone(),
474            },
475            received.clone(),
476        )
477        .await
478        .unwrap();
479
480        runtime.subscribe(&topic1, actor_ref).await.unwrap();
481
482        // Wait for subscription to be processed
483        sleep(Duration::from_millis(50)).await;
484
485        // Try to subscribe with different type to same topic name - should fail
486        #[derive(Clone)]
487        struct OtherMessage;
488        impl crate::actor::ActorMessage for OtherMessage {}
489        impl CloneableMessage for OtherMessage {}
490
491        let topic2 = Topic::<OtherMessage>::new(topic_name);
492
493        struct OtherActor;
494        #[async_trait]
495        impl Actor for OtherActor {
496            type Msg = OtherMessage;
497            type State = ();
498            type Arguments = ();
499
500            async fn pre_start(
501                &self,
502                _myself: ActorRef<Self::Msg>,
503                _args: Self::Arguments,
504            ) -> Result<Self::State, ActorProcessingErr> {
505                Ok(())
506            }
507
508            async fn handle(
509                &self,
510                _myself: ActorRef<Self::Msg>,
511                _message: Self::Msg,
512                _state: &mut Self::State,
513            ) -> Result<(), ActorProcessingErr> {
514                Ok(())
515            }
516        }
517
518        let (other_ref, _) = Actor::spawn(None, OtherActor, ()).await.unwrap();
519
520        // This should fail due to type mismatch
521        let result = runtime.subscribe(&topic2, other_ref).await;
522
523        // The subscribe method should return an error for type mismatch
524        assert!(result.is_err());
525
526        // Verify it's the correct error type
527        if let Err(RuntimeError::TopicTypeMismatch(topic, _)) = result {
528            assert_eq!(topic, topic_name);
529        } else {
530            panic!("Expected TopicTypeMismatch error");
531        }
532
533        // Shutdown
534        runtime.stop().await.unwrap();
535        runtime_task.abort();
536    }
537
538    #[tokio::test]
539    async fn test_message_ordering() {
540        let runtime = SingleThreadedRuntime::new(Some(10));
541        let runtime_handle = runtime.clone();
542
543        // Start runtime in background
544        let runtime_task = tokio::spawn(async move { runtime_handle.run().await });
545
546        // Create test actor that tracks message order
547        let received = Arc::new(Mutex::new(Vec::new()));
548        let (actor_ref, _actor_handle) = Actor::spawn(
549            None,
550            TestActor {
551                received: received.clone(),
552            },
553            received.clone(),
554        )
555        .await
556        .unwrap();
557
558        // Subscribe to topic
559        let topic = Topic::<TestMessage>::new("order_test");
560        runtime.subscribe(&topic, actor_ref).await.unwrap();
561
562        // Publish multiple messages rapidly
563        for i in 0..10 {
564            runtime
565                .publish(
566                    &topic,
567                    TestMessage {
568                        content: format!("Message {i}"),
569                    },
570                )
571                .await
572                .unwrap();
573        }
574
575        // Wait for all messages to be processed
576        sleep(Duration::from_millis(200)).await;
577
578        // Verify messages were received in order
579        let received_msgs = received.lock().await;
580        assert_eq!(received_msgs.len(), 10);
581
582        for (i, msg) in received_msgs.iter().enumerate() {
583            assert_eq!(msg, &format!("Message {i}"));
584        }
585
586        // Shutdown
587        runtime.stop().await.unwrap();
588        runtime_task.abort();
589    }
590
591    #[tokio::test]
592    async fn test_runtime_multiple_topics() {
593        let runtime = SingleThreadedRuntime::new(Some(10));
594        let runtime_handle = runtime.clone();
595
596        // Start runtime in background
597        let runtime_task = tokio::spawn(async move { runtime_handle.run().await });
598
599        // Create multiple topics
600        let topic1 = Topic::<TestMessage>::new("topic1");
601        let topic2 = Topic::<TestMessage>::new("topic2");
602
603        let received1 = Arc::new(Mutex::new(Vec::new()));
604        let received2 = Arc::new(Mutex::new(Vec::new()));
605
606        let (actor_ref1, _) = Actor::spawn(
607            None,
608            TestActor {
609                received: received1.clone(),
610            },
611            received1.clone(),
612        )
613        .await
614        .unwrap();
615
616        let (actor_ref2, _) = Actor::spawn(
617            None,
618            TestActor {
619                received: received2.clone(),
620            },
621            received2.clone(),
622        )
623        .await
624        .unwrap();
625
626        // Subscribe to different topics
627        runtime.subscribe(&topic1, actor_ref1).await.unwrap();
628        runtime.subscribe(&topic2, actor_ref2).await.unwrap();
629        sleep(Duration::from_millis(50)).await;
630
631        // Publish to topic1
632        let message1 = TestMessage {
633            content: "topic1_message".to_string(),
634        };
635        runtime.publish(&topic1, message1).await.unwrap();
636        sleep(Duration::from_millis(50)).await;
637
638        // Publish to topic2
639        let message2 = TestMessage {
640            content: "topic2_message".to_string(),
641        };
642        runtime.publish(&topic2, message2).await.unwrap();
643        sleep(Duration::from_millis(50)).await;
644
645        // Verify messages
646        let received_msgs1 = received1.lock().await;
647        let received_msgs2 = received2.lock().await;
648
649        assert_eq!(received_msgs1.len(), 1);
650        assert_eq!(received_msgs1[0], "topic1_message");
651
652        assert_eq!(received_msgs2.len(), 1);
653        assert_eq!(received_msgs2[0], "topic2_message");
654
655        // Shutdown
656        runtime.stop().await.unwrap();
657        runtime_task.abort();
658    }
659
660    #[tokio::test]
661    async fn test_runtime_subscribe_multiple_actors_same_topic() {
662        let runtime = SingleThreadedRuntime::new(Some(10));
663        let runtime_handle = runtime.clone();
664
665        // Start runtime in background
666        let runtime_task = tokio::spawn(async move { runtime_handle.run().await });
667
668        let topic = Topic::<TestMessage>::new("shared_topic");
669
670        let received1 = Arc::new(Mutex::new(Vec::new()));
671        let received2 = Arc::new(Mutex::new(Vec::new()));
672
673        let (actor_ref1, _) = Actor::spawn(
674            None,
675            TestActor {
676                received: received1.clone(),
677            },
678            received1.clone(),
679        )
680        .await
681        .unwrap();
682
683        let (actor_ref2, _) = Actor::spawn(
684            None,
685            TestActor {
686                received: received2.clone(),
687            },
688            received2.clone(),
689        )
690        .await
691        .unwrap();
692
693        // Subscribe both actors to same topic
694        runtime.subscribe(&topic, actor_ref1).await.unwrap();
695        runtime.subscribe(&topic, actor_ref2).await.unwrap();
696        sleep(Duration::from_millis(50)).await;
697
698        // Publish message
699        let message = TestMessage {
700            content: "broadcast_message".to_string(),
701        };
702        runtime.publish(&topic, message).await.unwrap();
703        sleep(Duration::from_millis(100)).await;
704
705        // Both actors should receive the message
706        let received_msgs1 = received1.lock().await;
707        let received_msgs2 = received2.lock().await;
708
709        assert_eq!(received_msgs1.len(), 1);
710        assert_eq!(received_msgs1[0], "broadcast_message");
711
712        assert_eq!(received_msgs2.len(), 1);
713        assert_eq!(received_msgs2[0], "broadcast_message");
714
715        // Shutdown
716        runtime.stop().await.unwrap();
717        runtime_task.abort();
718    }
719
720    #[test]
721    fn test_runtime_config_creation() {
722        let config = RuntimeConfig {
723            queue_size: Some(100),
724        };
725        assert_eq!(config.queue_size, Some(100));
726    }
727
728    #[test]
729    fn test_runtime_id_generation() {
730        let runtime1 = SingleThreadedRuntime::new(None);
731        let runtime2 = SingleThreadedRuntime::new(None);
732
733        assert_ne!(runtime1.id(), runtime2.id());
734    }
735}