1use behavior::{Address, ShutdownEvent, ShutdownRequested, UserEvent};
4
5use crate::runtime::lifecycle::IncarnationReporter;
6use crate::{
7 DeliveryEndpoint, EventSender, LifecycleTransition, MailboxAnchor, MailboxSender, NoLifecycle,
8 RejectedDelivery,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
13#[error("actor mailbox closed")]
14pub struct MailboxDeliveryClosed;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
18pub enum ShutdownRequestError {
19 #[error("the composed event protocol declined shutdown construction")]
21 Unsupported,
22 #[error("the actor mailbox consumer has already retired")]
24 Closed,
25}
26
27pub struct ActorRef<A, S, L = NoLifecycle> {
40 address: A,
41 sender: S,
42 lifecycle: L,
43}
44
45impl<A, S> ActorRef<A, S, NoLifecycle> {
46 pub(crate) const fn new(address: A, sender: S) -> Self {
48 Self::with_lifecycle(address, sender, NoLifecycle)
49 }
50}
51
52impl<A, S, L> ActorRef<A, S, L> {
53 pub(crate) const fn with_lifecycle(address: A, sender: S, lifecycle: L) -> Self {
54 Self {
55 address,
56 sender,
57 lifecycle,
58 }
59 }
60
61 pub const fn address(&self) -> &A {
63 &self.address
64 }
65}
66
67impl<A, E, L> ActorRef<A, MailboxSender<E>, L> {
68 pub(crate) fn sender_anchor(&self) -> MailboxAnchor<E> {
69 self.sender.anchor()
70 }
71}
72
73impl<A, S, L> ActorRef<A, S, L>
74where
75 A: Address + Send + Sync,
76 S: EventSender + Sync,
77 S::Event: UserEvent<Addr = A>,
78 <S::Event as UserEvent>::Message: Send,
79 L: Sync,
80{
81 pub async fn send(
93 &self,
94 from: A,
95 message: <S::Event as UserEvent>::Message,
96 ) -> Result<(), S::Error> {
97 self.sender
98 .send(<S::Event as UserEvent>::user(from, message))
99 .await
100 }
101}
102
103impl<A, E, L> ActorRef<A, MailboxSender<E>, L>
104where
105 E: ShutdownEvent,
106 L: IncarnationReporter,
107{
108 pub fn request_shutdown(&self) -> Result<(), ShutdownRequestError> {
120 let event =
121 E::shutdown_requested(ShutdownRequested).ok_or(ShutdownRequestError::Unsupported)?;
122 self.sender
123 .send_control(event)
124 .map_err(|_| ShutdownRequestError::Closed)?;
125 self.lifecycle.emit(LifecycleTransition::ShutdownRequested);
126 Ok(())
127 }
128}
129
130impl<A, E, L> DeliveryEndpoint<A, <E as UserEvent>::Message> for ActorRef<A, MailboxAnchor<E>, L>
131where
132 A: Address + Send + Sync,
133 E: UserEvent<Addr = A> + Send,
134 <E as UserEvent>::Message: Send,
135 L: Sync,
136{
137 type Error = MailboxDeliveryClosed;
138
139 async fn deliver(
140 &self,
141 from: A,
142 message: <E as UserEvent>::Message,
143 ) -> Result<(), RejectedDelivery<<E as UserEvent>::Message, Self::Error>> {
144 let event = E::user(from, message);
145 match self.sender.send(event).await {
146 Ok(()) => Ok(()),
147 Err(closed) => {
148 let Ok(user) = closed.0.into_user() else {
149 unreachable!("UserEvent::user must round-trip through UserEvent::into_user")
150 };
151 Err(RejectedDelivery::new(user.message, MailboxDeliveryClosed))
152 }
153 }
154 }
155}
156
157impl<A: Clone, S: Clone, L: Clone> Clone for ActorRef<A, S, L> {
158 fn clone(&self) -> Self {
159 Self::with_lifecycle(
160 self.address.clone(),
161 self.sender.clone(),
162 self.lifecycle.clone(),
163 )
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use std::convert::Infallible;
170 use std::sync::{Arc, Mutex};
171
172 use behavior::{MailAddr, ShutdownEvent, ShutdownProtocol, ShutdownRequested, User, UserEvent};
173
174 use super::{ActorRef, MailboxDeliveryClosed};
175 use crate::{
176 DeliveryEndpoint, EventSender, EventSource, MailboxConfig, RejectedDelivery,
177 ShutdownRequestError,
178 };
179
180 struct Message(String);
181
182 #[derive(Clone)]
183 struct Sender<E>(Arc<Mutex<Vec<E>>>);
184
185 impl<E: Send> EventSender for Sender<E> {
186 type Event = E;
187 type Error = Infallible;
188
189 async fn send(&self, event: E) -> Result<(), Self::Error> {
190 self.0.lock().expect("event lock").push(event);
191 Ok(())
192 }
193 }
194
195 #[tokio::test]
196 async fn moves_a_non_clone_message_directly_into_the_actor_event() {
197 let events: Arc<Mutex<Vec<User<MailAddr, Message>>>> = Arc::new(Mutex::new(Vec::new()));
198 let actor_ref = ActorRef::new(MailAddr(9), Sender(events.clone()));
199
200 actor_ref
201 .send(MailAddr(7), Message(String::from("hello")))
202 .await
203 .unwrap();
204
205 assert_eq!(*actor_ref.address(), MailAddr(9));
206 let events = events.lock().expect("event lock");
207 assert_eq!(events[0].from, MailAddr(7));
208 assert_eq!(events[0].message.0, "hello");
209 }
210
211 #[tokio::test]
212 async fn resolved_closed_mailbox_returns_the_exact_non_clone_message() {
213 let (sender, source) = MailboxConfig::bounded(1).create::<User<MailAddr, Message>>();
214 let endpoint = ActorRef::new(MailAddr(9), sender.anchor());
215 drop(source);
216
217 let RejectedDelivery { message, error } =
218 DeliveryEndpoint::deliver(&endpoint, MailAddr(7), Message(String::from("owned")))
219 .await
220 .expect_err("retired mailbox must reject delivery");
221
222 assert_eq!(message.0, "owned");
223 assert_eq!(error, MailboxDeliveryClosed);
224 }
225
226 #[tokio::test]
227 async fn publishes_shutdown_through_the_priority_lane() {
228 let (sender, mut source) =
229 MailboxConfig::bounded(1).create::<ShutdownProtocol<User<MailAddr, Message>>>();
230 let actor_ref = ActorRef::new(MailAddr(9), sender);
231
232 actor_ref.request_shutdown().unwrap();
233
234 assert!(matches!(
235 source.next().await,
236 Some(ShutdownProtocol::ShutdownRequested(ShutdownRequested))
237 ));
238 }
239
240 struct Declined;
241
242 impl ShutdownEvent for Declined {
243 fn shutdown_requested(_event: ShutdownRequested) -> Option<Self> {
244 None
245 }
246 }
247
248 impl UserEvent for Declined {
249 type Addr = MailAddr;
250 type Message = Message;
251
252 fn user(_from: MailAddr, _message: Message) -> Self {
253 Self
254 }
255
256 fn into_user(self) -> Result<User<MailAddr, Message>, Self> {
257 Err(self)
258 }
259 }
260
261 #[test]
262 fn distinguishes_declined_construction_from_closed_delivery() {
263 let (declined, _source) = MailboxConfig::bounded(1).create::<Declined>();
264 let declined = ActorRef::new(MailAddr(1), declined);
265 assert_eq!(
266 declined.request_shutdown(),
267 Err(ShutdownRequestError::Unsupported)
268 );
269
270 let (closed, source) =
271 MailboxConfig::bounded(1).create::<ShutdownProtocol<User<MailAddr, Message>>>();
272 drop(source);
273 let closed = ActorRef::new(MailAddr(2), closed);
274 assert_eq!(closed.request_shutdown(), Err(ShutdownRequestError::Closed));
275 }
276}