monarch_hyperactor/
context.rs1use std::sync::Arc;
10
11use hyperactor::Instance;
12use hyperactor::context;
13use hyperactor_mesh::comm::multicast::CastInfo;
14use ndslice::Extent;
15use ndslice::Point;
16use pyo3::exceptions::PyRuntimeError;
17use pyo3::prelude::*;
18
19use crate::actor::ExecutionTracker;
20use crate::actor::PythonActor;
21use crate::actor::root_client_actor;
22use crate::mailbox::PyMailbox;
23use crate::proc::PyActorAddr;
24use crate::runtime;
25use crate::shape::PyPoint;
26
27#[pyclass(name = "Instance", module = "monarch._src.actor.actor_mesh")]
28pub struct PyInstance {
29 inner: Instance<PythonActor>,
30 #[pyo3(get, set)]
31 proc_mesh: Option<Py<PyAny>>,
32 #[pyo3(get, set, name = "_controller_controller")]
33 controller_controller: Option<Py<PyAny>>,
34 #[pyo3(get, set)]
35 pub(crate) rank: PyPoint,
36 #[pyo3(get, set, name = "_children")]
37 children: Option<Py<PyAny>>,
38
39 #[pyo3(get, set, name = "name")]
40 name: String,
41 #[pyo3(get, set, name = "class_name")]
42 class_name: Option<String>,
43 #[pyo3(get, set, name = "creator")]
44 creator: Option<Py<PyAny>>,
45
46 #[pyo3(get, set, name = "_mock_tensor_engine_factory")]
47 mock_tensor_engine_factory: Option<Py<PyAny>>,
48
49 execution_tracker: Option<Arc<ExecutionTracker>>,
54}
55
56impl Clone for PyInstance {
57 fn clone(&self) -> Self {
58 PyInstance {
59 inner: self.inner.clone_for_py(),
60 proc_mesh: self.proc_mesh.clone(),
61 controller_controller: self.controller_controller.clone(),
62 rank: self.rank.clone(),
63 children: self.children.clone(),
64 name: self.name.clone(),
65 class_name: self.class_name.clone(),
66 creator: self.creator.clone(),
67 mock_tensor_engine_factory: self.mock_tensor_engine_factory.clone(),
68 execution_tracker: self.execution_tracker.clone(),
69 }
70 }
71}
72
73impl std::ops::Deref for PyInstance {
74 type Target = Instance<PythonActor>;
75
76 fn deref(&self) -> &Self::Target {
77 &self.inner
78 }
79}
80
81#[pymethods]
82impl PyInstance {
83 #[getter]
84 pub(crate) fn _mailbox(&self) -> PyMailbox {
85 PyMailbox {
86 inner: self.inner.mailbox_for_py().clone(),
87 }
88 }
89
90 #[getter]
91 pub fn actor_id(&self) -> PyActorAddr {
92 let actor_id: hyperactor::ActorAddr = self.inner.self_addr().clone();
93 actor_id.into()
94 }
95
96 #[pyo3(signature = (reason = None))]
97 fn abort(&self, reason: Option<&str>) -> PyResult<()> {
98 let reason = reason.unwrap_or("(no reason provided)");
99 Ok(self.inner.abort(reason).map_err(anyhow::Error::from)?)
100 }
101
102 #[pyo3(signature = (reason = None))]
103 fn kill(&self, reason: Option<&str>) -> PyResult<()> {
104 let reason = reason.unwrap_or("(no reason provided)");
105 Ok(self.inner.kill(reason).map_err(anyhow::Error::from)?)
106 }
107
108 #[pyo3(signature = (reason = None))]
109 fn stop(&self, reason: Option<&str>) -> PyResult<()> {
110 tracing::info!(actor_id = %self.inner.self_addr(), "stopping PyInstance");
111 let reason = reason.unwrap_or("(no reason provided)");
112 self.inner
113 .stop(reason)
114 .map_err(|e| PyRuntimeError::new_err(e.to_string()))
115 }
116
117 #[pyo3(signature = (reason = None))]
121 fn stop_and_wait(&self, reason: Option<&str>) -> PyResult<crate::pytokio::PyPythonTask> {
122 let reason = reason.unwrap_or("shutdown").to_string();
123 let actor_id = self.inner.self_addr().clone();
124 let proc = self.inner.proc().clone();
125 crate::pytokio::PyPythonTask::new(async move {
126 let status_rx = proc.stop_actor(actor_id.id(), reason);
127 if let Some(mut rx) = status_rx {
128 let _ = rx.wait_for(|s| s.is_terminal()).await;
129 }
130 if let Err(e) = proc.flush().await {
131 tracing::warn!(%actor_id, "stop_and_wait: flush failed: {}", e);
132 }
133 Ok(())
134 })
135 }
136
137 fn set_system(&self) {
143 self.inner.set_system();
144 }
145
146 #[pyo3(name = "_debug_skip_next_ordering_seq")]
158 fn debug_skip_next_ordering_seq(&self, receiver: &PyActorAddr, count: u64) {
159 use crate::actor::PythonMessage;
160 let port_addr = receiver
161 .inner
162 .port_addr(hyperactor::Port::handler::<PythonMessage>());
163 self.inner.debug_skip_next_ordering_seq(&port_addr, count);
164 }
165
166 fn _execution_start(&self, name: String) -> u64 {
172 match &self.execution_tracker {
173 Some(tracker) => tracker.start(name),
174 None => 0,
175 }
176 }
177
178 fn _execution_finish(&self, token: u64) {
179 if let Some(tracker) = &self.execution_tracker {
180 tracker.finish(token);
181 }
182 }
183}
184
185impl PyInstance {
186 pub fn into_instance(self) -> Instance<PythonActor> {
187 self.inner
188 }
189
190 pub(crate) fn set_execution_tracker(&mut self, tracker: Arc<ExecutionTracker>) {
193 self.execution_tracker = Some(tracker);
194 }
195}
196
197impl<I: context::Actor<A = PythonActor>> From<I> for PyInstance {
198 fn from(ins: I) -> Self {
199 PyInstance {
200 inner: ins.instance().clone_for_py(),
201 proc_mesh: None,
202 controller_controller: None,
203 rank: PyPoint::new(0, Extent::unity().into()),
204 children: None,
205 name: "root".to_string(),
206 class_name: None,
207 creator: None,
208 mock_tensor_engine_factory: None,
209 execution_tracker: None,
210 }
211 }
212}
213
214#[pyclass(name = "Context", module = "monarch._src.actor.actor_mesh")]
215pub struct PyContext {
216 instance: Py<PyInstance>,
217 rank: Point,
218 recording_span: Option<tracing::Span>,
223}
224
225#[pymethods]
226impl PyContext {
227 #[getter]
228 fn actor_instance(&self) -> &Py<PyInstance> {
229 &self.instance
230 }
231
232 #[getter]
233 fn message_rank(&self) -> PyPoint {
234 self.rank.clone().into()
235 }
236
237 #[staticmethod]
238 fn _root_client_context(py: Python<'_>) -> PyResult<PyContext> {
239 let _guard = runtime::get_tokio_runtime().enter();
240 let instance: PyInstance = root_client_actor(py).into();
241 Ok(PyContext {
242 instance: instance.into_pyobject(py)?.into(),
243 rank: Extent::unity().point_of_rank(0).unwrap(),
244 recording_span: None,
245 })
246 }
247
248 #[staticmethod]
252 fn _from_instance(py: Python<'_>, instance: PyInstance) -> PyResult<PyContext> {
253 Ok(PyContext {
254 instance: instance.into_pyobject(py)?.into(),
255 rank: Extent::unity().point_of_rank(0).unwrap(),
256 recording_span: None,
257 })
258 }
259}
260
261impl PyContext {
262 pub(crate) fn new<T: hyperactor::actor::Actor>(
263 cx: &hyperactor::Context<T>,
264 instance: Py<PyInstance>,
265 ) -> PyContext {
266 PyContext {
267 instance,
268 rank: cx.cast_point(),
269 recording_span: Some(cx.recording_span()),
270 }
271 }
272
273 pub(crate) fn recording_span(&self) -> Option<&tracing::Span> {
278 self.recording_span.as_ref()
279 }
280
281 #[doc(hidden)]
285 pub fn for_test(py: Python<'_>, recording_span: Option<tracing::Span>) -> PyResult<PyContext> {
286 let mut ctx = Self::_root_client_context(py)?;
287 ctx.recording_span = recording_span;
288 Ok(ctx)
289 }
290}
291
292pub fn register_python_bindings(hyperactor_mod: &Bound<'_, PyModule>) -> PyResult<()> {
293 hyperactor_mod.add_class::<PyInstance>()?;
294 hyperactor_mod.add_class::<PyContext>()?;
295 Ok(())
296}