monarch_hyperactor/code_sync/
auto_reload.rs1use std::sync::Arc;
10
11use anyhow::Result;
12use async_trait::async_trait;
13use hyperactor as reference;
14use hyperactor::Actor;
15use hyperactor::Context;
16use hyperactor::Endpoint as _;
17use hyperactor::Handler;
18use hyperactor::RemoteSpawn;
19use hyperactor_config::Flattrs;
20use monarch_types::SerializablePyErr;
21use pyo3::prelude::*;
22use serde::Deserialize;
23use serde::Serialize;
24use typeuri::Named;
25
26use crate::runtime::GilSite;
27use crate::runtime::monarch_with_gil_blocking;
28
29#[derive(Debug, Clone, Named, Serialize, Deserialize)]
31pub struct AutoReloadMessage {
32 pub result: reference::PortRef<Result<(), String>>,
33}
34wirevalue::register_type!(AutoReloadMessage);
35
36#[derive(Debug, Clone, Named, Serialize, Deserialize)]
38pub struct AutoReloadParams {}
39wirevalue::register_type!(AutoReloadParams);
40
41#[derive(Debug)]
43#[hyperactor::export(handlers = [AutoReloadMessage])]
44#[hyperactor::spawnable]
45pub struct AutoReloadActor {
46 state: Result<(Arc<Py<PyAny>>, Py<PyAny>), SerializablePyErr>,
47}
48
49impl Actor for AutoReloadActor {}
50
51#[async_trait]
52impl RemoteSpawn for AutoReloadActor {
53 type Params = AutoReloadParams;
54
55 async fn new(Self::Params {}: Self::Params, _environment: Flattrs) -> Result<Self> {
56 AutoReloadActor::new().await
57 }
58}
59
60impl AutoReloadActor {
61 pub(crate) async fn new() -> Result<Self, anyhow::Error> {
62 Ok(Self {
63 state: tokio::task::spawn_blocking(move || {
64 monarch_with_gil_blocking(GilSite::CodeSync, |py| {
65 Self::create_state(py).map_err(SerializablePyErr::from_fn(py))
66 })
67 })
68 .await?,
69 })
70 }
71
72 fn create_state(py: Python) -> PyResult<(Arc<Py<PyAny>>, Py<PyAny>)> {
73 let auto_reload_module = py.import("monarch._src.actor.code_sync.auto_reload")?;
75 let auto_reloader_class = auto_reload_module.getattr("AutoReloader")?;
76
77 let reloader = auto_reloader_class.call0()?;
78
79 let sys_audit_import_hook_class = auto_reload_module.getattr("SysAuditImportHook")?;
81 let import_callback = reloader.getattr("import_callback")?;
82 let hook_guard = sys_audit_import_hook_class.call_method1("install", (import_callback,))?;
83
84 Ok((Arc::new(reloader.into()), hook_guard.into()))
85 }
86
87 fn reload(py: Python, py_reloader: &Py<PyAny>) -> PyResult<()> {
88 let reloader = py_reloader.bind(py);
89 let changed_modules: Vec<String> = reloader.call_method0("reload_changes")?.extract()?;
90 if !changed_modules.is_empty() {
91 eprintln!("reloaded modules: {:?}", changed_modules);
92 }
93 Ok(())
94 }
95}
96
97#[async_trait]
98impl Handler<AutoReloadMessage> for AutoReloadActor {
99 async fn handle(
100 &mut self,
101 cx: &Context<Self>,
102 AutoReloadMessage { result }: AutoReloadMessage,
103 ) -> Result<()> {
104 let res = async {
106 let py_reloader: Arc<_> = self.state.as_ref().map_err(Clone::clone)?.0.clone();
107 tokio::task::spawn_blocking(move || {
108 monarch_with_gil_blocking(GilSite::CodeSync, |py| {
109 Self::reload(py, py_reloader.as_ref()).map_err(SerializablePyErr::from_fn(py))
110 })
111 })
112 .await??;
113 anyhow::Ok(())
114 }
115 .await;
116 result.post(cx, res.map_err(|e| format!("{:#?}", e)));
117 Ok(())
118 }
119}