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;
8use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
9use crate::server::connection::ConnectionSupervisor;
10use crate::server::connection::services::{ChannelCluster, LiminalConnectionServices};
11use crate::server::listener::ServerListener;
12use crate::server::shutdown::{ShutdownHandle, register_signal_handlers, run_shutdown_sequence};
13
14pub fn run(config_path: &Path) -> Result<(), ServerError> {
20 if config_path.as_os_str().is_empty() {
21 return Err(ServerError::ConfigLoad {
22 message: "configuration path is empty".to_owned(),
23 });
24 }
25
26 let config = load_config(config_path)?;
27
28 crate::metrics::init();
32
33 let readiness = SharedReadinessState::new(ReadinessState::default());
34 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
35 let shutdown_handle = ShutdownHandle::new();
36 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
37
38 let services = Arc::new(LiminalConnectionServices::from_config(&config)?);
42 let channel_cluster = services.channel_cluster().clone();
43 let auth_token = config
47 .auth
48 .as_ref()
49 .map(|auth| auth.token.clone().into_bytes());
50 let connection_supervisor = ConnectionSupervisor::with_services_and_auth(services, auth_token)?;
51
52 readiness.set_cluster_configured(config.cluster.is_some());
57 let cluster_handle = match config.cluster.as_ref() {
58 Some(cluster_config) => Some(start_cluster(&channel_cluster, cluster_config, &readiness)?),
59 None => None,
60 };
61
62 let mut listener = ServerListener::bind(&config, connection_supervisor)?;
63 readiness.set_config_loaded(true);
64 readiness.set_listener_bound(true);
65
66 tracing::debug!(
67 config_path = %config_path.display(),
68 listen_address = %config.listen_address,
69 health_listen_address = %health_server.local_addr(),
70 "liminal server configuration validated"
71 );
72
73 tracing::info!(
74 listen_address = %listener.local_addr(),
75 health_listen_address = %health_server.local_addr(),
76 "liminal server started"
77 );
78
79 shutdown_handle.wait();
80 readiness.set_listener_bound(false);
81
82 if let Some(mut cluster_handle) = cluster_handle {
86 cluster_handle.shutdown();
87 }
88
89 let supervisor = listener.supervisor();
90 let shutdown_result = run_shutdown_sequence(&mut listener, &supervisor, config.drain_timeout());
91 drop(signal_registration);
92 health_server.shutdown()?;
93 shutdown_result
94}
95
96fn start_cluster(
108 channel_cluster: &ChannelCluster,
109 cluster_config: &ClusterConfig,
110 readiness: &SharedReadinessState,
111) -> Result<ClusterHandle, ServerError> {
112 let resolver = channel_cluster
113 .resolver()
114 .cloned()
115 .ok_or_else(|| ServerError::ClusterJoin {
116 message: "clustering configured but channel supervisor has no distribution resolver"
117 .to_owned(),
118 })?;
119 let scheduler = channel_cluster.supervisor().scheduler();
120 let supervisor = channel_cluster.supervisor().clone();
121 let readiness = readiness.clone();
122 cluster::start(
123 &scheduler,
124 resolver,
125 cluster_config,
126 move |sync| {
127 supervisor.install_observer(Arc::new(sync));
128 },
129 move || readiness.set_cluster_membership_established(true),
130 )
131}
132
133#[cfg(test)]
134mod tests {
135 use std::net::SocketAddr;
136
137 use super::{ChannelCluster, ClusterConfig, SharedReadinessState, start_cluster};
138 use crate::ServerError;
139 use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
140 use crate::server::connection::services::LiminalConnectionServices;
141
142 fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
146 Ok(LiminalConnectionServices::empty()?
147 .channel_cluster()
148 .clone())
149 }
150
151 fn clustered_but_unmet_readiness() -> SharedReadinessState {
152 SharedReadinessState::new(ReadinessState::new(
153 true,
154 true,
155 ClusterReadiness::Configured {
156 membership_established: false,
157 },
158 ))
159 }
160
161 fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
162 let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
163 Ok(ClusterConfig {
164 node_name: "node-under-test@127.0.0.1".to_owned(),
165 listen_address,
166 seed_nodes: Vec::new(),
167 cookie: "runtime-test-cookie".to_owned(),
168 })
169 }
170
171 #[test]
172 fn failed_cluster_start_leaves_membership_unestablished()
173 -> Result<(), Box<dyn std::error::Error>> {
174 let readiness = clustered_but_unmet_readiness();
175 let channel_cluster = unclustered_channel_cluster()?;
176 let config = sample_cluster_config()?;
177
178 let result = start_cluster(&channel_cluster, &config, &readiness);
181 assert!(
182 result.is_err(),
183 "start_cluster must fail without a distribution resolver"
184 );
185
186 let status = readiness_check(&readiness.snapshot());
188 assert!(
189 !status.ready,
190 "readiness must remain not-ready after a failed start"
191 );
192 assert!(
193 status
194 .unmet_conditions
195 .contains(&ReadinessCondition::ClusterMembershipEstablished),
196 "cluster membership gate must stay unmet after a failed start"
197 );
198
199 Ok(())
200 }
201}