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}