1use std::fmt;
12
13use hyperactor_config::Flattrs;
14use serde::Deserialize;
15use serde::Serialize;
16
17use crate::ActorAddr;
18use crate::PortAddr;
19use crate::context;
20use crate::mailbox::PortLocation;
21
22#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, typeuri::Named)]
24pub enum EndpointLocation {
25 Actor(ActorAddr),
27 Port(PortAddr),
29 Local {
31 actor: ActorAddr,
33 message_type: String,
35 },
36}
37
38impl EndpointLocation {
39 pub fn actor_addr(&self) -> ActorAddr {
41 match self {
42 Self::Actor(actor) => actor.clone(),
43 Self::Port(port) => port.actor_addr(),
44 Self::Local { actor, .. } => actor.clone(),
45 }
46 }
47}
48
49impl From<PortLocation> for EndpointLocation {
50 fn from(location: PortLocation) -> Self {
51 match location {
52 PortLocation::Bound(port) => Self::Port(port),
53 PortLocation::Unbound(actor, message_type) => Self::Local {
54 actor,
55 message_type: message_type.to_string(),
56 },
57 }
58 }
59}
60
61impl fmt::Display for EndpointLocation {
62 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63 match self {
64 Self::Actor(actor) => write!(f, "{}", actor),
65 Self::Port(port) => write!(f, "{}", port),
66 Self::Local {
67 actor,
68 message_type,
69 } => write!(f, "{}<{}>", actor, message_type),
70 }
71 }
72}
73
74pub trait Endpoint<M>: crate::private::Sealed {
80 fn endpoint_location(&self) -> EndpointLocation;
82
83 fn post<C>(self, cx: &C, message: M)
85 where
86 C: context::Actor;
87}
88
89pub trait RemoteEndpoint<M>: Endpoint<M> {
94 fn post_with_headers<C>(self, cx: &C, headers: Flattrs, message: M)
96 where
97 C: context::Actor;
98}
99
100#[cfg(test)]
101mod tests {
102 use async_trait::async_trait;
103 use hyperactor_config::Flattrs;
104 use hyperactor_config::declare_attrs;
105 use tokio::sync::mpsc;
106 use typeuri::Named;
107
108 use super::*;
109 use crate::Actor;
110 use crate::Handler;
111 use crate::PortRef;
112 use crate::actor::Referable;
113 use crate::actor::RemoteHandles;
114 use crate::context::Mailbox as _;
115 use crate::proc::Context;
116 use crate::proc::Proc;
117
118 declare_attrs! {
119 attr ENDPOINT_TEST_HEADER: u64;
120 }
121
122 #[derive(Debug)]
123 struct EchoActor {
124 tx: PortRef<u64>,
125 }
126
127 #[async_trait]
128 impl Actor for EchoActor {}
129
130 #[async_trait]
131 impl Handler<u64> for EchoActor {
132 async fn handle(&mut self, cx: &Context<Self>, message: u64) -> anyhow::Result<()> {
133 Endpoint::post(&self.tx, cx, message);
134 Ok(())
135 }
136 }
137
138 struct TestBehavior;
139
140 impl Named for TestBehavior {
141 fn typename() -> &'static str {
142 "hyperactor::endpoint::tests::TestBehavior"
143 }
144 }
145
146 impl Referable for TestBehavior {}
147 impl RemoteHandles<u64> for TestBehavior {}
148
149 #[tokio::test]
150 async fn test_endpoint_actor_handle() {
151 let proc = Proc::isolated();
152 let client = proc.client("client");
153 let (tx, mut rx) = client.open_port();
154 let handle = proc.spawn(EchoActor { tx: tx.bind() });
155
156 Endpoint::post(&handle, &client, 123u64);
157
158 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
159 }
160
161 #[tokio::test]
162 async fn test_endpoint_port_handle() {
163 let proc = Proc::isolated();
164 let client = proc.client("client");
165 let (tx, mut rx) = client.open_port();
166
167 Endpoint::post(&tx, &client, 123u64);
168
169 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
170 }
171
172 #[tokio::test]
173 async fn test_endpoint_once_port_handle() {
174 let proc = Proc::isolated();
175 let client = proc.client("client");
176 let (tx, rx) = client.open_once_port();
177
178 Endpoint::post(tx, &client, 123u64);
179
180 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
181 }
182
183 #[tokio::test]
184 async fn test_endpoint_actor_ref() {
185 let proc = Proc::isolated();
186 let (client, actor_ref, mut rx) = proc
187 .attach_actor::<TestBehavior, u64>("remote_actor")
188 .expect("attach actor should succeed");
189
190 Endpoint::post(&actor_ref, &client, 123u64);
191
192 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
193 }
194
195 #[tokio::test]
196 async fn test_endpoint_port_ref() {
197 let proc = Proc::isolated();
198 let client = proc.client("client");
199 let (tx, mut rx) = client.open_port();
200 let port_ref = tx.bind();
201
202 Endpoint::post(&port_ref, &client, 123u64);
203
204 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
205 }
206
207 #[tokio::test]
208 async fn test_endpoint_once_port_ref() {
209 let proc = Proc::isolated();
210 let client = proc.client("client");
211 let (tx, rx) = client.open_once_port();
212 let port_ref = tx.bind();
213
214 Endpoint::post(port_ref, &client, 123u64);
215
216 assert_eq!(rx.recv().await.expect("message should arrive"), 123);
217 }
218
219 #[tokio::test]
220 async fn test_remote_endpoint_headers() {
221 let proc = Proc::isolated();
222 let client = proc.client("client");
223 let (observed_tx, mut observed_rx) = mpsc::unbounded_channel();
224 let port =
225 client
226 .mailbox()
227 .open_handler_enqueue_port(move |headers: Flattrs, message: u64| {
228 observed_tx
229 .send((
230 headers
231 .get(ENDPOINT_TEST_HEADER)
232 .expect("header should be present"),
233 message,
234 ))
235 .expect("test receiver should be alive");
236 Ok(())
237 });
238 let port_ref = port.bind();
239 let mut headers = Flattrs::new();
240 headers.set(ENDPOINT_TEST_HEADER, 456u64);
241
242 RemoteEndpoint::post_with_headers(&port_ref, &client, headers, 123u64);
243
244 assert_eq!(
245 observed_rx.recv().await.expect("message should arrive"),
246 (456, 123)
247 );
248 }
249}