allwright/
client_runtime.rs1use std::sync::{Arc, Mutex, OnceLock};
2
3use crate::proto::engine_service_client::EngineServiceClient;
4
5use super::bootstrap::{ensure_runtime_ready, shutdown_managed_server};
6use super::types::{Error, Result, RuntimeClient};
7
8const DEFAULT_SERVER_ADDR: &str = "http://127.0.0.1:50051";
9const SERVER_ADDR_ENV_VAR: &str = "ALLWRIGHT_SERVER_ADDR";
10
11static RUNTIME: OnceLock<Mutex<Option<Arc<RuntimeClient>>>> = OnceLock::new();
12static SERVER_ADDR_OVERRIDE: OnceLock<Mutex<Option<String>>> = OnceLock::new();
13
14pub async fn ping() -> Result<String> {
15 let runtime = get_runtime().await?;
16 let mut engine = runtime.engine.clone();
17 let response = engine.ping(tonic::Request::new(crate::proto::PingRequest {})).await?;
18 Ok(response.into_inner().message)
19}
20
21pub fn set_server_addr(server_addr: impl Into<String>) -> Result<()> {
22 let normalized = normalize_server_addr(&server_addr.into());
23 let mut override_slot = server_addr_override_slot()
24 .lock()
25 .map_err(|_| Error::new("server address override lock is poisoned"))?;
26 *override_slot = Some(normalized);
27 drop(override_slot);
28
29 let mut runtime = runtime_slot()
30 .lock()
31 .map_err(|_| Error::new("runtime singleton lock is poisoned"))?;
32 *runtime = None;
33 shutdown_managed_server()?;
34 Ok(())
35}
36
37pub async fn shutdown() {
38 if let Ok(mut runtime) = runtime_slot().lock() {
39 *runtime = None;
40 }
41 let _ = shutdown_managed_server();
42}
43
44pub(crate) async fn get_runtime() -> Result<Arc<RuntimeClient>> {
45 if let Ok(runtime) = runtime_slot().lock() {
46 if let Some(existing) = runtime.as_ref() {
47 return Ok(Arc::clone(existing));
48 }
49 }
50
51 let endpoint = configured_server_addr();
52 let resolved_endpoint = ensure_runtime_ready(&endpoint).await?;
53 let engine = EngineServiceClient::connect(resolved_endpoint).await?;
54 let runtime = Arc::new(RuntimeClient { engine });
55
56 let mut slot = runtime_slot()
57 .lock()
58 .map_err(|_| Error::new("runtime singleton lock is poisoned"))?;
59 if let Some(existing) = slot.as_ref() {
60 return Ok(Arc::clone(existing));
61 }
62 *slot = Some(Arc::clone(&runtime));
63 Ok(runtime)
64}
65
66fn runtime_slot() -> &'static Mutex<Option<Arc<RuntimeClient>>> {
67 RUNTIME.get_or_init(|| Mutex::new(None))
68}
69
70fn server_addr_override_slot() -> &'static Mutex<Option<String>> {
71 SERVER_ADDR_OVERRIDE.get_or_init(|| Mutex::new(None))
72}
73
74fn configured_server_addr() -> String {
75 if let Ok(server_addr_override) = server_addr_override_slot().lock() {
76 if let Some(server_addr) = server_addr_override.as_ref() {
77 return server_addr.clone();
78 }
79 }
80
81 normalize_server_addr(
82 std::env::var(SERVER_ADDR_ENV_VAR)
83 .ok()
84 .filter(|value| !value.trim().is_empty())
85 .as_deref()
86 .unwrap_or(DEFAULT_SERVER_ADDR),
87 )
88}
89
90fn normalize_server_addr(raw: &str) -> String {
91 let trimmed = raw.trim();
92 if trimmed.starts_with("http://") || trimmed.starts_with("https://") {
93 trimmed.to_string()
94 } else {
95 format!("http://{trimmed}")
96 }
97}