liminal_server/server/
runtime.rs1use std::path::Path;
2use std::sync::Arc;
3
4use crate::ServerError;
5use crate::cluster::{self, ClusterHandle};
6use crate::config::file::load_config;
7use crate::config::types::{ClusterConfig, ServiceProfile};
8use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
9use crate::server::connection::ConnectionSupervisor;
10use crate::server::connection::services::{
11 ChannelCluster, LiminalConnectionServices, build_connection_services,
12};
13use crate::server::listener::ServerListener;
14use crate::server::shutdown::{ShutdownHandle, register_signal_handlers, run_shutdown_sequence};
15
16pub fn run(config_path: &Path) -> Result<(), ServerError> {
22 if config_path.as_os_str().is_empty() {
23 return Err(ServerError::ConfigLoad {
24 message: "configuration path is empty".to_owned(),
25 });
26 }
27
28 let config = load_config(config_path)?;
29
30 crate::metrics::init();
34
35 let readiness = SharedReadinessState::new(ReadinessState::default());
36 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
37 let shutdown_handle = ShutdownHandle::new();
38 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
39
40 let auth_token = config
45 .auth
46 .as_ref()
47 .map(|auth| auth.token.clone().into_bytes());
48
49 let (connection_supervisor, cluster_handle) = match config.services.profile()? {
57 ServiceProfile::Full => {
58 let services = Arc::new(LiminalConnectionServices::from_config(&config)?);
59 let channel_cluster = services.channel_cluster().clone();
60 let connection_supervisor =
61 ConnectionSupervisor::with_services_and_auth(services, auth_token)?;
62
63 readiness.set_cluster_configured(config.cluster.is_some());
68 let cluster_handle = match config.cluster.as_ref() {
69 Some(cluster_config) => {
70 Some(start_cluster(&channel_cluster, cluster_config, &readiness)?)
71 }
72 None => None,
73 };
74 (connection_supervisor, cluster_handle)
75 }
76 ServiceProfile::WorkerFrontDoor => {
77 let services = build_connection_services(&config)?;
78 let connection_supervisor =
79 ConnectionSupervisor::with_services_and_auth(services, auth_token)?;
80 readiness.set_cluster_configured(false);
81 (connection_supervisor, None)
82 }
83 };
84
85 let mut listener = ServerListener::bind(&config, connection_supervisor)?;
86 readiness.set_config_loaded(true);
87 readiness.set_listener_bound(true);
88
89 tracing::debug!(
90 config_path = %config_path.display(),
91 listen_address = %config.listen_address,
92 health_listen_address = %health_server.local_addr(),
93 "liminal server configuration validated"
94 );
95
96 tracing::info!(
97 listen_address = %listener.local_addr(),
98 health_listen_address = %health_server.local_addr(),
99 "liminal server started"
100 );
101
102 shutdown_handle.wait();
103 readiness.set_listener_bound(false);
104
105 if let Some(mut cluster_handle) = cluster_handle {
109 cluster_handle.shutdown();
110 }
111
112 let supervisor = listener.supervisor();
113 let shutdown_result = run_shutdown_sequence(&mut listener, &supervisor, config.drain_timeout());
114 drop(signal_registration);
115 health_server.shutdown()?;
116 shutdown_result
117}
118
119fn start_cluster(
131 channel_cluster: &ChannelCluster,
132 cluster_config: &ClusterConfig,
133 readiness: &SharedReadinessState,
134) -> Result<ClusterHandle, ServerError> {
135 let resolver = channel_cluster
136 .resolver()
137 .cloned()
138 .ok_or_else(|| ServerError::ClusterJoin {
139 message: "clustering configured but channel supervisor has no distribution resolver"
140 .to_owned(),
141 })?;
142 let scheduler = channel_cluster.supervisor().scheduler();
143 let supervisor = channel_cluster.supervisor().clone();
144 let readiness = readiness.clone();
145 cluster::start(
146 &scheduler,
147 resolver,
148 cluster_config,
149 move |sync| {
150 supervisor.install_observer(Arc::new(sync));
151 },
152 move || readiness.set_cluster_membership_established(true),
153 )
154}
155
156#[cfg(test)]
157mod tests {
158 use std::net::SocketAddr;
159
160 use super::{ChannelCluster, ClusterConfig, SharedReadinessState, start_cluster};
161 use crate::ServerError;
162 use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
163 use crate::server::connection::services::LiminalConnectionServices;
164
165 fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
169 Ok(LiminalConnectionServices::empty()?
170 .channel_cluster()
171 .clone())
172 }
173
174 fn clustered_but_unmet_readiness() -> SharedReadinessState {
175 SharedReadinessState::new(ReadinessState::new(
176 true,
177 true,
178 ClusterReadiness::Configured {
179 membership_established: false,
180 },
181 ))
182 }
183
184 fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
185 let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
186 Ok(ClusterConfig {
187 node_name: "node-under-test@127.0.0.1".to_owned(),
188 listen_address,
189 seed_nodes: Vec::new(),
190 cookie: "runtime-test-cookie".to_owned(),
191 })
192 }
193
194 #[test]
195 fn failed_cluster_start_leaves_membership_unestablished()
196 -> Result<(), Box<dyn std::error::Error>> {
197 let readiness = clustered_but_unmet_readiness();
198 let channel_cluster = unclustered_channel_cluster()?;
199 let config = sample_cluster_config()?;
200
201 let result = start_cluster(&channel_cluster, &config, &readiness);
204 assert!(
205 result.is_err(),
206 "start_cluster must fail without a distribution resolver"
207 );
208
209 let status = readiness_check(&readiness.snapshot());
211 assert!(
212 !status.ready,
213 "readiness must remain not-ready after a failed start"
214 );
215 assert!(
216 status
217 .unmet_conditions
218 .contains(&ReadinessCondition::ClusterMembershipEstablished),
219 "cluster membership gate must stay unmet after a failed start"
220 );
221
222 Ok(())
223 }
224}