1use std::fmt;
2use std::path::Path;
3use std::sync::Arc;
4use std::time::Instant;
5
6use crate::ServerError;
7use crate::auth_pass::PassVerifier;
8use crate::cluster::{self, ClusterHandle};
9use crate::config::file::load_config;
10use crate::config::types::{ClusterConfig, ServiceProfile};
11use crate::health::{ReadinessState, SharedReadinessState, start_health_server};
12use crate::server::connection::ConnectionSupervisor;
13use crate::server::connection::WebSocketListener;
14use crate::server::connection::services::{
15 ChannelCluster, LiminalConnectionServices, build_connection_services,
16};
17use crate::server::listener::ServerListener;
18use crate::server::self_streams::cost::ServerMetadata;
19use crate::server::self_streams::owner::PublisherRuntime;
20use crate::server::self_streams::sink::SelfStreams;
21use crate::server::shutdown::{
22 ShutdownHandle, register_signal_handlers, run_shutdown_sequence_before_flush,
23};
24
25fn configured_authentication(
26 config: &crate::config::types::ServerConfig,
27) -> Result<(Option<Vec<u8>>, Option<PassVerifier>), ServerError> {
28 let auth_token = config
29 .auth
30 .as_ref()
31 .map(|auth| auth.token.clone().into_bytes());
32 let pass_verifier = config
33 .auth
34 .as_ref()
35 .map(PassVerifier::from_auth)
36 .transpose()?
37 .flatten();
38 Ok((auth_token, pass_verifier))
39}
40
41pub fn run_participant_log_migration(
54 config_path: &Path,
55) -> Result<crate::server::participant::ParticipantLogMigration, ServerError> {
56 let config = load_config(config_path)?;
57 let Some(persistence_path) = config.persistence_path.as_deref() else {
58 return Err(ServerError::ConfigValidation {
59 message: "the participant log migration needs persistence_path; an ephemeral store has no logs to migrate".to_owned(),
60 });
61 };
62 let Some(participant) = config.participant.as_ref() else {
63 return Err(ServerError::ConfigValidation {
64 message: "the participant log migration needs the [participant] section".to_owned(),
65 });
66 };
67 let store = crate::server::connection::services::open_persistent_store(persistence_path)?;
68 Ok(crate::server::participant::migrate_participant_logs(
69 &store,
70 participant,
71 )?)
72}
73
74pub fn run(config_path: &Path) -> Result<(), ServerError> {
80 let mut stages = StartStages::new();
81 validate_config_path(config_path)?;
82
83 let config = load_config(config_path)?;
84 stages.mark("config");
85
86 crate::metrics::init();
90
91 let readiness = SharedReadinessState::new(ReadinessState::default());
92 let health_server = start_health_server(config.health_listen_address, readiness.clone())?;
93 stages.mark("health");
94 let shutdown_handle = ShutdownHandle::new();
95 let signal_registration = register_signal_handlers(shutdown_handle.clone())?;
96
97 let (auth_token, pass_verifier) = configured_authentication(&config)?;
102 stages.mark("auth");
103
104 let mut self_stream_publisher = None;
112 let (connection_supervisor, cluster_handle) = match config.services.profile()? {
113 ServiceProfile::Full => {
114 let services = Arc::new(LiminalConnectionServices::from_config(&config)?);
115 stages.mark("services");
116 if let Some(record) = services.unloadable_conversation_record() {
126 health_server.install_unloadable_record(record);
127 }
128 if let Some(reissuer) = services.credential_reissuer() {
133 health_server.install_credential_reissuer(reissuer);
134 }
135 let channel_cluster = services.channel_cluster().clone();
136 let signals_owner = Arc::clone(&services);
137 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
138 services,
139 auth_token,
140 pass_verifier,
141 config.limits,
142 shutdown_handle.clone(),
143 )?;
144 stages.mark("supervisor");
145
146 self_stream_publisher = start_self_stream_publisher(
147 &signals_owner,
148 &connection_supervisor,
149 &config,
150 &shutdown_handle,
151 )?;
152 stages.mark("incarnation");
153
154 let cluster_handle =
159 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), &readiness)?;
160 stages.mark("cluster");
161 (connection_supervisor, cluster_handle)
162 }
163 ServiceProfile::WorkerFrontDoor => {
164 let services = build_connection_services(&config)?;
165 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
166 services,
167 auth_token,
168 pass_verifier,
169 config.limits,
170 shutdown_handle.clone(),
171 )?;
172 readiness.set_cluster_configured(false);
173 stages.mark("supervisor");
174 (connection_supervisor, None)
175 }
176 };
177
178 readiness.track_admission(connection_supervisor.admission_readiness());
185
186 let (mut listener, mut websocket_listener) =
187 bind_listeners(&config, connection_supervisor, &readiness)?;
188 stages.mark("listeners");
189
190 tracing::debug!(
191 config_path = %config_path.display(),
192 listen_address = %config.listen_address,
193 health_listen_address = %health_server.local_addr(),
194 "liminal server configuration validated"
195 );
196
197 tracing::info!(
198 listen_address = %listener.local_addr(),
199 health_listen_address = %health_server.local_addr(),
200 start_stages = %stages,
201 "liminal server started"
202 );
203
204 shutdown_handle.wait();
205 readiness.set_listener_bound(false);
206
207 if let Some(mut cluster_handle) = cluster_handle {
211 cluster_handle.shutdown();
212 }
213
214 let supervisor = listener.supervisor();
215 let shutdown_result = run_shutdown_sequence_before_flush(
216 &mut listener,
217 websocket_listener.as_mut(),
218 &supervisor,
219 config.drain_timeout(),
220 || finish_self_stream_publisher(&mut self_stream_publisher),
221 );
222 let participant_fatal = supervisor.participant_service_fatal();
223 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
224 drop(websocket_listener);
225 drop(signal_registration);
226 raise_in_precedence(
229 health_server.shutdown(),
230 raise_in_precedence(publisher_shutdown, shutdown_result),
231 )?;
232 participant_fatal?.map_or(Ok(()), |fatal| {
233 Err(ServerError::ParticipantServiceFatal { fatal })
234 })
235}
236
237fn bind_listeners(
240 config: &crate::config::types::ServerConfig,
241 supervisor: ConnectionSupervisor,
242 readiness: &SharedReadinessState,
243) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
244 let listener = ServerListener::bind(config, supervisor)?;
245 let websocket_listener = match config.websocket.as_ref() {
250 Some(websocket_config) => Some(WebSocketListener::bind(
251 websocket_config,
252 listener.supervisor(),
253 )?),
254 None => None,
255 };
256 readiness.set_config_loaded(true);
257 readiness.set_listener_bound(true);
258 Ok((listener, websocket_listener))
259}
260
261struct StartStages {
264 began: Instant,
265 marks: Vec<(&'static str, u128)>,
266}
267
268impl StartStages {
269 fn new() -> Self {
270 Self {
271 began: Instant::now(),
272 marks: Vec::with_capacity(8),
273 }
274 }
275
276 fn mark(&mut self, name: &'static str) {
278 self.marks.push((name, self.began.elapsed().as_millis()));
279 }
280}
281
282impl fmt::Display for StartStages {
285 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
286 let mut before = 0;
287 for (index, (name, at)) in self.marks.iter().enumerate() {
288 if index > 0 {
289 formatter.write_str(", ")?;
290 }
291 write!(formatter, "{name} {} ({at})", at.saturating_sub(before))?;
292 before = *at;
293 }
294 Ok(())
295 }
296}
297
298fn validate_config_path(config_path: &Path) -> Result<(), ServerError> {
299 if config_path.as_os_str().is_empty() {
300 return Err(ServerError::ConfigLoad {
301 message: "configuration path is empty".to_owned(),
302 });
303 }
304 Ok(())
305}
306
307pub(in crate::server) fn start_self_stream_publisher(
308 services: &Arc<LiminalConnectionServices>,
309 supervisor: &ConnectionSupervisor,
310 config: &crate::config::types::ServerConfig,
311 shutdown: &ShutdownHandle,
312) -> Result<Option<PublisherRuntime>, ServerError> {
313 bind_self_streams(services, supervisor, config, shutdown)?
314 .map(|streams| {
315 PublisherRuntime::start(services.signals().wake(), streams, shutdown.clone())
316 })
317 .transpose()
318}
319
320fn finish_self_stream_publisher(
321 publisher: &mut Option<PublisherRuntime>,
322) -> Result<(), ServerError> {
323 publisher.as_mut().map_or(Ok(()), PublisherRuntime::finish)
324}
325
326fn start_configured_cluster(
327 channels: &ChannelCluster,
328 config: Option<&ClusterConfig>,
329 readiness: &SharedReadinessState,
330) -> Result<Option<ClusterHandle>, ServerError> {
331 readiness.set_cluster_configured(config.is_some());
332 config
333 .map(|config| start_cluster(channels, config, readiness))
334 .transpose()
335}
336
337pub(in crate::server) fn bind_self_streams(
339 services: &Arc<LiminalConnectionServices>,
340 supervisor: &ConnectionSupervisor,
341 config: &crate::config::types::ServerConfig,
342 shutdown: &ShutdownHandle,
343) -> Result<Option<Arc<SelfStreams>>, ServerError> {
344 let signals = services.signals();
345 supervisor.install_signals(signals.clone())?;
346 let Some(incarnation) = supervisor.current_server_incarnation()? else {
347 crate::metrics::self_streams_disabled();
348 tracing::warn!(
349 "SELF_STREAMS_REQUIRE_PARTICIPANT: self streams disabled because no participant is configured"
350 );
351 crate::metrics::console_streams_disabled();
355 tracing::warn!(
356 "CONSOLE_STREAMS_REQUIRE_PARTICIPANT: console streams disabled because no participant is configured"
357 );
358 return Ok(None);
359 };
360 services.install_self_stream_channels()?;
361 services
363 .console()
364 .incarnation_started(std::time::Instant::now())
365 .map_err(|error| ServerError::ListenerAccept {
366 message: format!("console hub: {error}"),
367 })?;
368 let metadata = ServerMetadata::from_config(config, incarnation).map_err(|error| {
369 ServerError::ConfigValidation {
370 message: error.to_string(),
371 }
372 })?;
373 let streams = Arc::new(SelfStreams::new(
374 metadata,
375 services,
376 signals.wake(),
377 shutdown.clone(),
378 config.services.self_streams,
379 ));
380 signals.install(Arc::clone(&streams))?;
381 services.install_console_channels()?;
382 signals.enable_console()?;
383 let wake = signals.wake();
384 wake.install_source(services, &streams)?;
385 let counts = services.store_counts();
386 let names = ["durable", "ephemeral-dedup"];
387 let configured = names
388 .get(..counts.store_count())
389 .ok_or_else(|| SelfStreams::failure("unexpected number of counted stores"))?;
390 counts.install_observer(configured, wake).map_err(|error| {
391 SelfStreams::failure(&format!("store count observer installation: {error}"))
392 })?;
393 Ok(Some(streams))
394}
395
396fn raise_in_precedence(
401 health_shutdown: Result<(), ServerError>,
402 shutdown_sequence: Result<(), ServerError>,
403) -> Result<(), ServerError> {
404 if let Err(error) = health_shutdown {
405 if let Err(sequence_error) = &shutdown_sequence {
406 tracing::error!(
407 %sequence_error,
408 "shutdown sequence failed; the health endpoint shutdown failure takes the exit code"
409 );
410 }
411 return Err(error);
412 }
413 shutdown_sequence
414}
415
416fn start_cluster(
428 channel_cluster: &ChannelCluster,
429 cluster_config: &ClusterConfig,
430 readiness: &SharedReadinessState,
431) -> Result<ClusterHandle, ServerError> {
432 let resolver = channel_cluster
433 .resolver()
434 .cloned()
435 .ok_or_else(|| ServerError::ClusterJoin {
436 message: "clustering configured but channel supervisor has no distribution resolver"
437 .to_owned(),
438 })?;
439 let scheduler = channel_cluster.supervisor().scheduler();
440 let observer = Arc::clone(channel_cluster.observer());
441 let readiness = readiness.clone();
442 cluster::start(
443 &scheduler,
444 resolver,
445 cluster_config,
446 move |sync| {
447 let peers = Arc::clone(&observer);
450 if !sync.set_peer_listener(Arc::new(move |name, present| peers.peer(name, present))) {
451 tracing::error!("cluster peer listener was already installed");
452 }
453 if let Err(error) = observer.set_cluster(Arc::new(sync)) {
456 tracing::error!(%error, "cluster observer was not installed");
457 }
458 },
459 move || readiness.set_cluster_membership_established(true),
460 )
461}
462
463#[cfg(test)]
464mod tests {
465 use std::net::SocketAddr;
466
467 use super::{ChannelCluster, ClusterConfig, SharedReadinessState, StartStages, start_cluster};
468 use crate::ServerError;
469 use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
470 use crate::server::connection::services::LiminalConnectionServices;
471
472 #[test]
475 fn start_stages_read_as_lengths_and_ends() {
476 let stages = StartStages {
477 began: std::time::Instant::now(),
478 marks: vec![("config", 3), ("health", 4), ("services", 2_104)],
479 };
480 assert_eq!(
481 stages.to_string(),
482 "config 3 (3), health 1 (4), services 2100 (2104)"
483 );
484 }
485
486 fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
490 Ok(LiminalConnectionServices::empty()?
491 .channel_cluster()
492 .clone())
493 }
494
495 fn clustered_but_unmet_readiness() -> SharedReadinessState {
496 SharedReadinessState::new(ReadinessState::new(
497 true,
498 true,
499 ClusterReadiness::Configured {
500 membership_established: false,
501 },
502 ))
503 }
504
505 fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
506 let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
507 Ok(ClusterConfig {
508 node_name: "node-under-test@127.0.0.1".to_owned(),
509 listen_address,
510 seed_nodes: Vec::new(),
511 cookie: "runtime-test-cookie".to_owned(),
512 })
513 }
514
515 #[test]
516 fn failed_cluster_start_leaves_membership_unestablished()
517 -> Result<(), Box<dyn std::error::Error>> {
518 let readiness = clustered_but_unmet_readiness();
519 let channel_cluster = unclustered_channel_cluster()?;
520 let config = sample_cluster_config()?;
521
522 let result = start_cluster(&channel_cluster, &config, &readiness);
525 assert!(
526 result.is_err(),
527 "start_cluster must fail without a distribution resolver"
528 );
529
530 let status = readiness_check(&readiness.snapshot());
532 assert!(
533 !status.ready,
534 "readiness must remain not-ready after a failed start"
535 );
536 assert!(
537 status
538 .unmet_conditions
539 .contains(&ReadinessCondition::ClusterMembershipEstablished),
540 "cluster membership gate must stay unmet after a failed start"
541 );
542
543 Ok(())
544 }
545}