1use std::fmt;
12use std::sync::Arc;
13
14use crate as hyperactor;
15use crate::Actor;
16use crate::ActorAddr;
17use crate::ActorRef;
18use crate::Data;
19use crate::Message;
20use crate::RemoteMessage;
21use crate::actor::ActorHandle;
22use crate::actor::ActorStatus;
23use crate::actor::AnyActorHandle;
24use crate::actor::Binds;
25use crate::context;
26use crate::context::Mailbox as MailboxContext;
27use crate::id::Uid;
28use crate::mailbox::Mailbox;
29use crate::mailbox::MessageEnvelope;
30use crate::mailbox::OncePortHandle;
31use crate::mailbox::OncePortReceiver;
32use crate::mailbox::PortHandle;
33use crate::mailbox::PortReceiver;
34use crate::mailbox::Undeliverable;
35use crate::ordering::Sequencer;
36use crate::proc::HandlerPorts;
37use crate::proc::Instance;
38use crate::proc::Proc;
39
40#[derive(Debug, Default)]
42#[hyperactor::export]
43pub struct ClientActor;
44
45impl Actor for ClientActor {}
46
47impl Binds<ClientActor> for () {
48 fn bind(_ports: &HandlerPorts<ClientActor>) {}
49}
50
51pub struct Client {
57 instance: Instance<ClientActor>,
58 lifecycle: Arc<ClientLifecycle>,
59}
60
61struct ClientLifecycle {
62 instance: Instance<ClientActor>,
63}
64
65impl Drop for ClientLifecycle {
66 fn drop(&mut self) {
67 self.instance.close_client("client dropped");
68 }
69}
70
71impl fmt::Debug for Client {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 f.debug_struct("Client")
74 .field("self_addr", self.self_addr())
75 .finish()
76 }
77}
78
79impl Client {
80 pub(crate) fn new(instance: Instance<ClientActor>) -> Self {
81 let actor_id = instance.self_addr().clone();
82 instance.bind_handler_enqueue_port::<Undeliverable<MessageEnvelope>>(move |_, message| {
83 tracing::error!(
84 actor_id = %actor_id,
85 undeliverable = ?message,
86 "client received undeliverable message: crashing"
87 );
88 std::process::exit(1);
89 });
90
91 Self {
92 lifecycle: Arc::new(ClientLifecycle {
93 instance: instance.clone_for_py(),
94 }),
95 instance,
96 }
97 }
98
99 pub fn self_addr(&self) -> &ActorAddr {
101 self.instance.self_addr()
102 }
103
104 pub fn proc(&self) -> &Proc {
106 self.instance.proc()
107 }
108
109 pub fn open_port<M: Message>(&self) -> (PortHandle<M>, PortReceiver<M>) {
111 self.instance.open_port()
112 }
113
114 pub fn open_once_port<M: Message>(&self) -> (OncePortHandle<M>, OncePortReceiver<M>) {
116 self.instance.open_once_port()
117 }
118
119 pub fn bind_handler_port<M: RemoteMessage>(&self) -> (PortHandle<M>, PortReceiver<M>) {
121 self.instance.mailbox().bind_handler_port()
122 }
123
124 pub fn bind<R: Binds<ClientActor>>(&self) -> ActorRef<R> {
126 self.instance.bind()
127 }
128
129 pub fn child(&self) -> Client {
131 self.instance.child_client()
132 }
133
134 pub fn spawn<A: Actor>(&self, actor: A) -> ActorHandle<A> {
136 self.instance.spawn(actor)
137 }
138
139 pub fn spawn_with_label<A: Actor>(&self, label: &str, actor: A) -> ActorHandle<A> {
141 self.instance.spawn_with_label(label, actor)
142 }
143
144 pub fn spawn_with_uid<A: Actor>(&self, uid: Uid, actor: A) -> anyhow::Result<ActorHandle<A>> {
146 self.instance.spawn_with_uid(uid, actor)
147 }
148
149 pub async fn gspawn(&self, actor_type: &str, params: Data) -> anyhow::Result<AnyActorHandle> {
151 self.instance.gspawn(actor_type, params).await
152 }
153
154 pub async fn gspawn_uid(
156 &self,
157 actor_type: &str,
158 uid: Uid,
159 params: Data,
160 ) -> anyhow::Result<AnyActorHandle> {
161 self.instance.gspawn_uid(actor_type, uid, params).await
162 }
163
164 pub fn sequencer(&self) -> &Sequencer {
166 self.instance.sequencer()
167 }
168
169 pub fn status(&self) -> tokio::sync::watch::Receiver<ActorStatus> {
171 self.instance.status()
172 }
173}
174
175impl Clone for Client {
176 fn clone(&self) -> Self {
177 Self {
178 instance: self.instance.clone_for_py(),
179 lifecycle: Arc::clone(&self.lifecycle),
180 }
181 }
182}
183
184impl context::Mailbox for Client {
185 fn mailbox(&self) -> &Mailbox {
186 MailboxContext::mailbox(&self.instance)
187 }
188}
189
190impl context::Mailbox for &Client {
191 fn mailbox(&self) -> &Mailbox {
192 MailboxContext::mailbox(&self.instance)
193 }
194}
195
196impl context::Actor for Client {
197 type A = ClientActor;
198
199 fn instance(&self) -> &Instance<ClientActor> {
200 &self.instance
201 }
202}
203
204impl context::Actor for &Client {
205 type A = ClientActor;
206
207 fn instance(&self) -> &Instance<ClientActor> {
208 &self.instance
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use crate::Proc;
215 use crate::context;
216
217 #[test]
218 fn client_ids_are_fresh_instances() {
219 let proc = Proc::isolated();
220
221 let first = proc.client("caller");
222 let second = proc.client("caller");
223 let anonymous = proc.client("");
224
225 assert!(first.self_addr().id().uid().is_instance());
226 assert!(second.self_addr().id().uid().is_instance());
227 assert!(anonymous.self_addr().id().uid().is_instance());
228 assert_eq!(
229 first.self_addr().id().label().map(|label| label.as_str()),
230 Some("caller")
231 );
232 assert_eq!(anonymous.self_addr().id().label(), None);
233 assert_ne!(first.self_addr().id(), second.self_addr().id());
234 }
235
236 #[test]
237 fn client_binds_return_handler_without_actor_loop() {
238 let proc = Proc::isolated();
239 let client = proc.client("client");
240
241 assert!(
242 context::Mailbox::mailbox(&client)
243 .bound_return_handle()
244 .is_some(),
245 "taskless clients should install a return handler"
246 );
247 }
248
249 #[tokio::test]
250 async fn dropping_client_closes_mailbox_but_preserves_accepted_messages() {
251 let proc = Proc::isolated();
252 let receiver = proc.client("receiver");
253 let sender = proc.client("sender");
254 let (port, mut rx) = receiver.open_port::<u64>();
255
256 port.try_post(&sender, 1).unwrap();
257 drop(receiver);
258
259 assert_eq!(rx.recv().await.unwrap(), 1);
260 assert!(port.try_post(&sender, 2).is_err());
261 assert_eq!(rx.try_recv().unwrap(), None);
262 }
263}