Skip to main content

acton_core/actor/managed_agent/
started.rs

1/*
2 * Copyright (c) 2024. Govcraft
3 *
4 * Licensed under either of
5 *   * Apache License, Version 2.0 (the "License");
6 *     you may not use this file except in compliance with the License.
7 *     You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
8 *   * MIT license: http://opensource.org/licenses/MIT
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the applicable License for the specific language governing permissions and
14 * limitations under that License.
15 */
16
17use std::any::type_name_of_val;
18use std::fmt::Debug;
19use std::time::{Duration, Instant};
20
21use futures::future::join_all;
22use futures::stream::{FuturesUnordered, StreamExt};
23use tracing::{instrument, trace}; // Removed unused error import
24
25use crate::actor::ManagedAgent;
26use crate::common::{Envelope, OutboundEnvelope, ReactorItem, ReactorMap};
27use crate::common::config::CONFIG;
28use crate::message::{BrokerRequestEnvelope, MessageAddress, SystemSignal};
29use crate::traits::AgentHandleInterface;
30
31/// Type-state marker for a [`ManagedAgent`] that is actively running and processing messages.
32///
33/// When a `ManagedAgent` is in the `Started` state, its main asynchronous task (`wake`)
34/// is running, receiving messages from its inbox and dispatching them to the appropriate
35/// handlers registered during the [`Idle`](super::Idle) state.
36///
37/// Agents in this state can create message envelopes using methods like [`ManagedAgent::new_envelope`]
38/// and [`ManagedAgent::new_parent_envelope`]. Interaction typically occurs via the agent's
39/// [`AgentHandle`].
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] // Add common derives
41pub struct Started;
42
43/// Implements methods specific to a `ManagedAgent` in the `Started` state.
44impl<Agent: Default + Send + Debug + 'static> ManagedAgent<Started, Agent> {
45    /// Creates a new [`OutboundEnvelope`] originating from this agent.
46    ///
47    /// This helper function constructs an envelope suitable for sending a message
48    /// from this agent to another recipient. The envelope's `return_address`
49    /// will be set to this agent's [`MessageAddress`]. The `recipient_address`
50    /// field will be `None` initially and should typically be set using the
51    /// envelope's methods before sending.
52    ///
53    /// # Returns
54    ///
55    /// An [`OutboundEnvelope`] configured with this agent as the sender.
56    /// Returns `None` only if the agent's handle somehow lacks an outbox, which
57    /// should not occur under normal circumstances.
58    pub fn new_envelope(&self) -> Option<OutboundEnvelope> {
59        self.cancellation_token.clone().map(|cancellation_token| {
60            OutboundEnvelope::new(
61                MessageAddress::new(self.handle.outbox.clone(), self.id.clone()),
62                cancellation_token,
63            )
64        })
65    }
66
67    /// Creates a new [`OutboundEnvelope`] addressed to this agent's parent.
68    ///
69    /// This is a convenience method for creating an envelope specifically for
70    /// replying or sending a message to the agent that supervises this one.
71    /// It clones the parent's return address information.
72    ///
73    /// # Returns
74    ///
75    /// *   `Some(OutboundEnvelope)`: An envelope configured to be sent to the parent,
76    ///     if this agent has a parent. The `return_address` will be the parent's address,
77    ///     and the `recipient_address` will be this agent's address.
78    /// *   `None`: If this agent does not have a parent (i.e., it's a top-level agent).
79    pub fn new_parent_envelope(&self) -> Option<OutboundEnvelope> {
80        // Only construct if both parent and cancellation_token exist
81        let cancellation_token = self.cancellation_token.clone()?;
82        self.parent.as_ref().map(|parent_handle| {
83            OutboundEnvelope::new_with_recipient(
84                MessageAddress::new(self.handle.outbox.clone(), self.id.clone()), // Self is sender
85                parent_handle.reply_address(), // Parent is recipient
86                cancellation_token,
87            )
88        })
89    }
90
91    // wake() and terminate() are internal implementation details (`pub(crate)` or private)
92    // and do not require public documentation.
93    #[instrument(skip(mutable_reactors, read_only_reactors, self))]
94    pub(crate) async fn wake(
95        &mut self,
96        mutable_reactors: ReactorMap<Agent>,
97        read_only_reactors: ReactorMap<Agent>,
98    ) {
99        (self.after_start)(self).await;
100        // Assert that cancellation_token always exists; it must never be missing.
101        assert!(
102            self.cancellation_token.is_some(),
103            "ManagedAgent in Started state must always have a cancellation_token"
104        );
105        let cancel_token = self.cancellation_token.as_ref().cloned().unwrap();
106        let mut cancel = Box::pin(cancel_token.cancelled());
107
108        let mut _terminate_signal_received = false;
109
110        use crate::common::config::CONFIG;
111        // Concurrent execution buffer for read-only handlers
112        let mut read_only_futures = FuturesUnordered::new();
113        let high_water_mark = CONFIG.limits.concurrent_handlers_high_water_mark;
114        let max_wait_duration = Duration::from_millis(CONFIG.timeouts.read_only_handler_flush_ms);
115        let mut last_flush_time = Instant::now();
116
117        loop {
118            tokio::select! {
119                // React immediately to cancellation
120                _ = &mut cancel => {
121                    trace!("Forceful cancellation triggered for agent: {}", self.id());
122                    // Flush any remaining read-only futures before breaking
123                    if !read_only_futures.is_empty() {
124                        trace!("Flushing {} remaining read-only futures on cancellation", read_only_futures.len());
125                        while let Some(_) = read_only_futures.next().await {}
126                    }
127                    break; // Immediate break on forceful cancellation.
128                }
129
130                // Check if we should flush read-only futures due to time limit
131                _ = tokio::time::sleep_until((last_flush_time + max_wait_duration).into()), if !read_only_futures.is_empty() => {
132                    trace!("Flushing {} read-only futures due to time limit", read_only_futures.len());
133                    while let Some(_) = read_only_futures.next().await {}
134                    last_flush_time = Instant::now();
135                }
136
137                incoming_opt = self.inbox.recv() => {
138                    let Some(incoming_envelope) = incoming_opt else { break; };
139                    let type_id;
140                    let mut envelope;
141                    trace!(
142                        "Received envelope from: {}",
143                        incoming_envelope.reply_to.sender.root
144                    );
145                    trace!(
146                        "Message type: {}",
147                        type_name_of_val(&incoming_envelope.message)
148                    );
149
150                    // Handle potential BrokerRequestEnvelope indirection
151                    if let Some(broker_request_envelope) = incoming_envelope
152                        .message
153                        .as_any()
154                        .downcast_ref::<BrokerRequestEnvelope>()
155                    {
156                        trace!("Processing message via BrokerRequestEnvelope");
157                        envelope = Envelope::new(
158                            broker_request_envelope.message.clone(), // Extract inner message
159                            incoming_envelope.reply_to.clone(),
160                            incoming_envelope.recipient.clone(),
161                        );
162                        type_id = broker_request_envelope.message.as_any().type_id(); // Use inner message TypeId
163                    } else {
164                        envelope = incoming_envelope;
165                        type_id = envelope.message.as_any().type_id();
166                    }
167
168                    // Dispatch to registered handler or handle system signals
169                    if let Some(reactor) = mutable_reactors.get(&type_id) {
170                        // Flush any pending read-only futures before processing mutable handlers
171                        if !read_only_futures.is_empty() {
172                            trace!("Flushing {} read-only futures before mutable handler", read_only_futures.len());
173                            while let Some(_) = read_only_futures.next().await {}
174                            last_flush_time = Instant::now();
175                        }
176
177                        match reactor.value() {
178                            ReactorItem::FutureReactor(fut) => {
179                                // Legacy handler: await, always Ok
180                                fut(self, &mut envelope).await;
181                            }
182                            ReactorItem::FutureReactorResult(fut) => {
183                                // New Result-based handler: await and trigger error handler on Err
184                                let result = fut(self, &mut envelope).await;
185                                if let Err((err, error_type_id)) = result {
186                                    let message_type_id = envelope.message.as_any().type_id();
187                                    if let Some(handler) =
188                                        self.error_handler_map.remove(&(message_type_id, error_type_id))
189                                    {
190                                        let fut = handler(self, &mut envelope, err.as_ref());
191                                        fut.await;
192                                        self.error_handler_map.insert((message_type_id, error_type_id), handler);
193                                    } else {
194                                        tracing::error!(
195                                            "Unhandled error from message handler in agent {}: {:?}",
196                                            self.id(),
197                                            err
198                                        );
199                                    }
200                                }
201                            }
202                            ReactorItem::FutureReactorReadOnly(_) => {
203                                // This should not happen - read-only handlers should be in read_only_reactors
204                                tracing::warn!("Found read-only handler in mutable_reactors map");
205                            }
206                            ReactorItem::FutureReactorReadOnlyResult(_) => {
207                                // This should not happen - read-only handlers should be in read_only_reactors
208                                tracing::warn!("Found read-only Result handler in mutable_reactors map");
209                            }
210                        }
211                    } else if let Some(reactor) = read_only_reactors.get(&type_id) {
212                        match reactor.value() {
213                            ReactorItem::FutureReactorReadOnly(fut) => {
214                                // Concurrent read-only handler: add to buffer
215                                let fut = fut(self, &mut envelope);
216                                read_only_futures.push(tokio::spawn(async move {
217                                    fut.await;
218                                }));
219
220                                // Check if we've hit the high water mark
221                                if read_only_futures.len() >= high_water_mark {
222                                    trace!("Flushing {} read-only futures due to high water mark", read_only_futures.len());
223                                    while let Some(_) = read_only_futures.next().await {}
224                                    last_flush_time = Instant::now();
225                                }
226                            }
227                            ReactorItem::FutureReactorReadOnlyResult(fut) => {
228                                // Concurrent read-only handler with error handling: add to buffer
229                                let fut = fut(self, &mut envelope);
230                                read_only_futures.push(tokio::spawn(async move {
231                                    if let Err((err, _error_type_id)) = fut.await {
232                                        // Note: Error handling for read-only handlers is not supported
233                                        // as we don't have access to the agent for error handlers
234                                        tracing::error!(
235                                            "Unhandled error from read-only message handler: {:?}",
236                                            err
237                                        );
238                                    }
239                                }));
240
241                                // Check if we've hit the high water mark
242                                if read_only_futures.len() >= high_water_mark {
243                                    trace!("Flushing {} read-only Result futures due to high water mark", read_only_futures.len());
244                                    while let Some(_) = read_only_futures.next().await {}
245                                    last_flush_time = Instant::now();
246                                }
247                            }
248                            _ => {
249                                // This should not happen - mutable handlers should be in mutable_reactors
250                                tracing::warn!("Found mutable handler in read_only_reactors map");
251                            }
252                        }
253                    } else if let Some(SystemSignal::Terminate) =
254                        envelope.message.as_any().downcast_ref::<SystemSignal>()
255                    {
256                        // Flush any remaining read-only futures before processing terminate
257                        if !read_only_futures.is_empty() {
258                            trace!("Flushing {} read-only futures before terminate", read_only_futures.len());
259                            while let Some(_) = read_only_futures.next().await {}
260                        }
261
262                        trace!("Terminate signal received for agent: {}. Closing inbox.", self.id());
263                        _terminate_signal_received = true; // Set flag
264                        (self.before_stop)(self).await; // Execute before_stop hook
265                        self.inbox.close(); // Close inbox to stop receiving new messages.
266                        // Do NOT break here. Allow loop to drain existing messages.
267                    } else {
268                        trace!(
269                            "No handler found for message type {:?} for agent {}",
270                            type_id,
271                            self.id()
272                        );
273                        // Optionally log or handle unknown message types
274                    }
275                }
276            }
277        }
278        // After loop breaks (either gracefully or forcefully), perform final termination steps.
279        trace!(
280            "Message loop finished for agent: {}. Initiating final termination.",
281            self.id()
282        );
283
284        // Flush any remaining read-only futures before final termination
285        if !read_only_futures.is_empty() {
286            trace!(
287                "Flushing {} remaining read-only futures before final termination",
288                read_only_futures.len()
289            );
290            while let Some(_) = read_only_futures.next().await {}
291        }
292
293        self.terminate().await; // Stop children and other cleanup.
294        (self.after_stop)(self).await;
295        trace!("Agent {} stopped.", self.id());
296    }
297
298    #[instrument(skip(self))]
299    async fn terminate(&mut self) {
300        trace!("Terminating children for agent: {}", self.id());
301        // Stop all child agents concurrently.
302        use std::time::Duration;
303        use tokio::time::timeout as tokio_timeout;
304
305        let timeout_ms = CONFIG.timeouts.agent_shutdown_timeout_ms as u64;
306
307        let stop_futures: Vec<_> = self
308            .handle
309            .children()
310            .iter()
311            .map(|item| {
312                let child_handle = item.value().clone();
313                async move {
314                    trace!("Sending stop signal to child: {}", child_handle.id());
315                    let stop_res =
316                        tokio_timeout(Duration::from_millis(timeout_ms), child_handle.stop()).await;
317                    match stop_res {
318                        Ok(Ok(())) => {
319                            trace!(
320                                "Stop signal sent to and child {} shut down successfully.",
321                                child_handle.id()
322                            );
323                        }
324                        Ok(Err(e)) => {
325                            tracing::error!(
326                                "Stop signal to child {} returned error: {:?}",
327                                child_handle.id(),
328                                e
329                            );
330                        }
331                        Err(_) => {
332                            tracing::error!(
333                                "Shutdown timeout for child {} after {} ms",
334                                child_handle.id(),
335                                timeout_ms
336                            );
337                        }
338                    }
339                }
340            })
341            .collect();
342
343        join_all(stop_futures).await; // Wait for all stop signals to be sent/processed.
344
345        trace!("All children stopped for agent: {}.", self.id());
346    }
347}