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::OncePortHandle;
30use crate::mailbox::OncePortReceiver;
31use crate::mailbox::PortHandle;
32use crate::mailbox::PortReceiver;
33use crate::ordering::Sequencer;
34use crate::proc::HandlerPorts;
35use crate::proc::Instance;
36use crate::proc::Proc;
37
38#[derive(Debug, Default)]
40#[hyperactor::export]
41pub struct ClientActor;
42
43impl Actor for ClientActor {}
44
45impl Binds<ClientActor> for () {
46 fn bind(_ports: &HandlerPorts<ClientActor>) {}
47}
48
49pub struct Client {
55 instance: Instance<ClientActor>,
56 lifecycle: Arc<ClientLifecycle>,
57}
58
59struct ClientLifecycle {
60 instance: Instance<ClientActor>,
61}
62
63impl Drop for ClientLifecycle {
64 fn drop(&mut self) {
65 self.instance.close_client("client dropped");
66 }
67}
68
69impl fmt::Debug for Client {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("Client")
72 .field("self_addr", self.self_addr())
73 .finish()
74 }
75}
76
77impl Client {
78 pub(crate) fn new(instance: Instance<ClientActor>) -> Self {
79 Self {
80 lifecycle: Arc::new(ClientLifecycle {
81 instance: instance.clone_for_py(),
82 }),
83 instance,
84 }
85 }
86
87 pub fn self_addr(&self) -> &ActorAddr {
89 self.instance.self_addr()
90 }
91
92 pub fn proc(&self) -> &Proc {
94 self.instance.proc()
95 }
96
97 pub fn open_port<M: Message>(&self) -> (PortHandle<M>, PortReceiver<M>) {
99 self.instance.open_port()
100 }
101
102 pub fn open_once_port<M: Message>(&self) -> (OncePortHandle<M>, OncePortReceiver<M>) {
104 self.instance.open_once_port()
105 }
106
107 pub fn bind_handler_port<M: RemoteMessage>(&self) -> (PortHandle<M>, PortReceiver<M>) {
109 self.instance.mailbox().bind_handler_port()
110 }
111
112 pub fn bind<R: Binds<ClientActor>>(&self) -> ActorRef<R> {
114 self.instance.bind()
115 }
116
117 pub fn child(&self) -> Client {
119 self.instance.child_client()
120 }
121
122 pub fn spawn<A: Actor>(&self, actor: A) -> ActorHandle<A> {
124 self.instance.spawn(actor)
125 }
126
127 pub fn spawn_with_label<A: Actor>(&self, label: &str, actor: A) -> ActorHandle<A> {
129 self.instance.spawn_with_label(label, actor)
130 }
131
132 pub fn spawn_with_uid<A: Actor>(&self, uid: Uid, actor: A) -> anyhow::Result<ActorHandle<A>> {
134 self.instance.spawn_with_uid(uid, actor)
135 }
136
137 pub async fn gspawn(&self, actor_type: &str, params: Data) -> anyhow::Result<AnyActorHandle> {
139 self.instance.gspawn(actor_type, params).await
140 }
141
142 pub async fn gspawn_uid(
144 &self,
145 actor_type: &str,
146 uid: Uid,
147 params: Data,
148 ) -> anyhow::Result<AnyActorHandle> {
149 self.instance.gspawn_uid(actor_type, uid, params).await
150 }
151
152 pub fn sequencer(&self) -> &Sequencer {
154 self.instance.sequencer()
155 }
156
157 pub fn status(&self) -> tokio::sync::watch::Receiver<ActorStatus> {
159 self.instance.status()
160 }
161}
162
163impl Clone for Client {
164 fn clone(&self) -> Self {
165 Self {
166 instance: self.instance.clone_for_py(),
167 lifecycle: Arc::clone(&self.lifecycle),
168 }
169 }
170}
171
172impl context::Mailbox for Client {
173 fn mailbox(&self) -> &Mailbox {
174 MailboxContext::mailbox(&self.instance)
175 }
176}
177
178impl context::Mailbox for &Client {
179 fn mailbox(&self) -> &Mailbox {
180 MailboxContext::mailbox(&self.instance)
181 }
182}
183
184impl context::Actor for Client {
185 type A = ClientActor;
186
187 fn instance(&self) -> &Instance<ClientActor> {
188 &self.instance
189 }
190}
191
192impl context::Actor for &Client {
193 type A = ClientActor;
194
195 fn instance(&self) -> &Instance<ClientActor> {
196 &self.instance
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::Proc;
203
204 #[test]
205 fn client_ids_are_fresh_instances() {
206 let proc = Proc::isolated();
207
208 let first = proc.client("caller");
209 let second = proc.client("caller");
210 let anonymous = proc.client("");
211
212 assert!(first.self_addr().id().uid().is_instance());
213 assert!(second.self_addr().id().uid().is_instance());
214 assert!(anonymous.self_addr().id().uid().is_instance());
215 assert_eq!(
216 first.self_addr().id().label().map(|label| label.as_str()),
217 Some("caller")
218 );
219 assert_eq!(anonymous.self_addr().id().label(), None);
220 assert_ne!(first.self_addr().id(), second.self_addr().id());
221 }
222
223 #[tokio::test]
224 async fn dropping_client_closes_mailbox_but_preserves_accepted_messages() {
225 let proc = Proc::isolated();
226 let receiver = proc.client("receiver");
227 let sender = proc.client("sender");
228 let (port, mut rx) = receiver.open_port::<u64>();
229
230 port.try_post(&sender, 1).unwrap();
231 drop(receiver);
232
233 assert_eq!(rx.recv().await.unwrap(), 1);
234 assert!(port.try_post(&sender, 2).is_err());
235 assert_eq!(rx.try_recv().unwrap(), None);
236 }
237}