lean_ctx/core/ocla/
runtime.rs1use axum::serve;
4use tokio::{net::TcpListener, task::JoinHandle};
5use tokio_util::sync::CancellationToken;
6use tracing::{debug, warn};
7
8use crate::core::config::OclaConfig;
9
10use super::OclaResult;
11use super::wire_api::ocla_router;
12
13pub struct OclaRuntime {
15 rest_handle: JoinHandle<()>,
16 cancel: CancellationToken,
17 rest_port: u16,
18}
19
20impl OclaRuntime {
21 pub async fn start(config: &OclaConfig) -> OclaResult<Self> {
23 let _ = config;
24 let listener = TcpListener::bind("127.0.0.1:0").await.map_err(|error| {
25 super::OclaError::InvalidRequest(format!("failed to bind OCLA REST listener: {error}"))
26 })?;
27 let rest_port = listener
28 .local_addr()
29 .map_err(|error| {
30 super::OclaError::InvalidRequest(format!(
31 "failed to inspect OCLA REST listener: {error}"
32 ))
33 })?
34 .port();
35 let cancel = CancellationToken::new();
36 let shutdown = cancel.clone();
37 let rest_handle = tokio::spawn(async move {
38 if let Err(error) = serve(listener, ocla_router())
39 .with_graceful_shutdown(shutdown.cancelled_owned())
40 .await
41 {
42 warn!(error = %error, "OCLA REST server stopped with an error");
43 }
44 });
45
46 Ok(Self {
47 rest_handle,
48 cancel,
49 rest_port,
50 })
51 }
52
53 pub async fn stop(self) -> OclaResult<()> {
55 self.cancel.cancel();
56 self.rest_handle.await.map_err(|error| {
57 super::OclaError::InvalidRequest(format!("OCLA REST task failed: {error}"))
58 })?;
59 debug!("OCLA REST runtime stopped");
60 Ok(())
61 }
62
63 #[must_use]
65 pub fn rest_port(&self) -> u16 {
66 self.rest_port
67 }
68
69 #[must_use]
71 pub fn is_running(&self) -> bool {
72 !self.rest_handle.is_finished()
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 #[tokio::test]
81 async fn start_and_stop_lifecycle() {
82 let runtime = OclaRuntime::start(&OclaConfig::default())
83 .await
84 .expect("runtime starts");
85 assert!(runtime.is_running());
86 runtime.stop().await.expect("runtime stops");
87 }
88
89 #[tokio::test]
90 async fn rest_port_is_nonzero() {
91 let runtime = OclaRuntime::start(&OclaConfig::default())
92 .await
93 .expect("runtime starts");
94 assert_ne!(runtime.rest_port(), 0);
95 runtime.stop().await.expect("runtime stops");
96 }
97
98 #[tokio::test]
99 async fn double_stop_is_idempotent() {
100 let runtime = OclaRuntime::start(&OclaConfig::default())
101 .await
102 .expect("runtime starts");
103 runtime.cancel.cancel();
104 runtime.cancel.cancel();
105 runtime.stop().await.expect("runtime stops");
106 }
107}