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 install_ephemeral_sweep_report(&services, &health_server)?;
117 if let Some(record) = services.unloadable_conversation_record() {
127 health_server.install_unloadable_record(record);
128 }
129 if let Some(reissuer) = services.credential_reissuer() {
134 health_server.install_credential_reissuer(reissuer);
135 }
136 let channel_cluster = services.channel_cluster().clone();
137 let signals_owner = Arc::clone(&services);
138 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
139 services,
140 auth_token,
141 pass_verifier,
142 config.limits,
143 shutdown_handle.clone(),
144 )?;
145 stages.mark("supervisor");
146
147 self_stream_publisher = start_self_stream_publisher(
148 &signals_owner,
149 &connection_supervisor,
150 &config,
151 &shutdown_handle,
152 )?;
153 stages.mark("incarnation");
154
155 let cluster_handle =
160 start_configured_cluster(&channel_cluster, config.cluster.as_ref(), &readiness)?;
161 stages.mark("cluster");
162 (connection_supervisor, cluster_handle)
163 }
164 ServiceProfile::WorkerFrontDoor => {
165 let services = build_connection_services(&config)?;
166 let connection_supervisor = ConnectionSupervisor::with_fatal_shutdown(
167 services,
168 auth_token,
169 pass_verifier,
170 config.limits,
171 shutdown_handle.clone(),
172 )?;
173 readiness.set_cluster_configured(false);
174 stages.mark("supervisor");
175 (connection_supervisor, None)
176 }
177 };
178
179 readiness.track_admission(connection_supervisor.admission_readiness());
186
187 let (mut listener, mut websocket_listener) =
188 bind_listeners(&config, connection_supervisor, &readiness)?;
189 stages.mark("listeners");
190
191 tracing::debug!(
192 config_path = %config_path.display(),
193 listen_address = %config.listen_address,
194 health_listen_address = %health_server.local_addr(),
195 "liminal server configuration validated"
196 );
197
198 tracing::info!(
199 listen_address = %listener.local_addr(),
200 health_listen_address = %health_server.local_addr(),
201 start_stages = %stages,
202 "liminal server started"
203 );
204
205 shutdown_handle.wait();
206 readiness.set_listener_bound(false);
207
208 if let Some(mut cluster_handle) = cluster_handle {
212 cluster_handle.shutdown();
213 }
214
215 let supervisor = listener.supervisor();
216 let shutdown_result = run_shutdown_sequence_before_flush(
217 &mut listener,
218 websocket_listener.as_mut(),
219 &supervisor,
220 config.drain_timeout(),
221 || finish_self_stream_publisher(&mut self_stream_publisher),
222 );
223 let participant_fatal = supervisor.participant_service_fatal();
224 let publisher_shutdown = finish_self_stream_publisher(&mut self_stream_publisher);
225 drop(websocket_listener);
226 drop(signal_registration);
227 raise_in_precedence(
230 health_server.shutdown(),
231 raise_in_precedence(publisher_shutdown, shutdown_result),
232 )?;
233 participant_fatal?.map_or(Ok(()), |fatal| {
234 Err(ServerError::ParticipantServiceFatal { fatal })
235 })
236}
237
238fn bind_listeners(
241 config: &crate::config::types::ServerConfig,
242 supervisor: ConnectionSupervisor,
243 readiness: &SharedReadinessState,
244) -> Result<(ServerListener, Option<WebSocketListener>), ServerError> {
245 let listener = ServerListener::bind(config, supervisor)?;
246 let websocket_listener = match config.websocket.as_ref() {
251 Some(websocket_config) => Some(WebSocketListener::bind(
252 websocket_config,
253 listener.supervisor(),
254 )?),
255 None => None,
256 };
257 readiness.set_config_loaded(true);
258 readiness.set_listener_bound(true);
259 Ok((listener, websocket_listener))
260}
261
262struct StartStages {
265 began: Instant,
266 marks: Vec<(&'static str, u128)>,
267}
268
269impl StartStages {
270 fn new() -> Self {
271 Self {
272 began: Instant::now(),
273 marks: Vec::with_capacity(8),
274 }
275 }
276
277 fn mark(&mut self, name: &'static str) {
279 self.marks.push((name, self.began.elapsed().as_millis()));
280 }
281}
282
283impl fmt::Display for StartStages {
286 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
287 let mut before = 0;
288 for (index, (name, at)) in self.marks.iter().enumerate() {
289 if index > 0 {
290 formatter.write_str(", ")?;
291 }
292 write!(formatter, "{name} {} ({at})", at.saturating_sub(before))?;
293 before = *at;
294 }
295 Ok(())
296 }
297}
298
299fn validate_config_path(config_path: &Path) -> Result<(), ServerError> {
300 if config_path.as_os_str().is_empty() {
301 return Err(ServerError::ConfigLoad {
302 message: "configuration path is empty".to_owned(),
303 });
304 }
305 Ok(())
306}
307
308pub(in crate::server) fn start_self_stream_publisher(
309 services: &Arc<LiminalConnectionServices>,
310 supervisor: &ConnectionSupervisor,
311 config: &crate::config::types::ServerConfig,
312 shutdown: &ShutdownHandle,
313) -> Result<Option<PublisherRuntime>, ServerError> {
314 bind_self_streams(services, supervisor, config, shutdown)?
315 .map(|streams| {
316 PublisherRuntime::start(services.signals().wake(), streams, shutdown.clone())
317 })
318 .transpose()
319}
320
321fn finish_self_stream_publisher(
322 publisher: &mut Option<PublisherRuntime>,
323) -> Result<(), ServerError> {
324 publisher.as_mut().map_or(Ok(()), PublisherRuntime::finish)
325}
326
327fn start_configured_cluster(
328 channels: &ChannelCluster,
329 config: Option<&ClusterConfig>,
330 readiness: &SharedReadinessState,
331) -> Result<Option<ClusterHandle>, ServerError> {
332 readiness.set_cluster_configured(config.is_some());
333 config
334 .map(|config| start_cluster(channels, config, readiness))
335 .transpose()
336}
337
338pub(in crate::server) fn bind_self_streams(
340 services: &Arc<LiminalConnectionServices>,
341 supervisor: &ConnectionSupervisor,
342 config: &crate::config::types::ServerConfig,
343 shutdown: &ShutdownHandle,
344) -> Result<Option<Arc<SelfStreams>>, ServerError> {
345 let signals = services.signals();
346 supervisor.install_signals(signals.clone())?;
347 let Some(incarnation) = supervisor.current_server_incarnation()? else {
348 crate::metrics::self_streams_disabled();
349 tracing::warn!(
350 "SELF_STREAMS_REQUIRE_PARTICIPANT: self streams disabled because no participant is configured"
351 );
352 crate::metrics::console_streams_disabled();
356 tracing::warn!(
357 "CONSOLE_STREAMS_REQUIRE_PARTICIPANT: console streams disabled because no participant is configured"
358 );
359 return Ok(None);
360 };
361 services.install_self_stream_channels()?;
362 services
364 .console()
365 .incarnation_started(std::time::Instant::now())
366 .map_err(|error| ServerError::ListenerAccept {
367 message: format!("console hub: {error}"),
368 })?;
369 let metadata = ServerMetadata::from_config(config, incarnation).map_err(|error| {
370 ServerError::ConfigValidation {
371 message: error.to_string(),
372 }
373 })?;
374 let streams = Arc::new(SelfStreams::new(
375 metadata,
376 services,
377 signals.wake(),
378 shutdown.clone(),
379 config.services.self_streams,
380 ));
381 signals.install(Arc::clone(&streams))?;
382 services.install_console_channels()?;
383 signals.enable_console()?;
384 let wake = signals.wake();
385 wake.install_source(services, &streams)?;
386 let counts = services.store_counts();
387 let names = ["durable", "ephemeral-dedup"];
388 let configured = names
389 .get(..counts.store_count())
390 .ok_or_else(|| SelfStreams::failure("unexpected number of counted stores"))?;
391 counts.install_observer(configured, wake).map_err(|error| {
392 SelfStreams::failure(&format!("store count observer installation: {error}"))
393 })?;
394 Ok(Some(streams))
395}
396
397fn raise_in_precedence(
402 health_shutdown: Result<(), ServerError>,
403 shutdown_sequence: Result<(), ServerError>,
404) -> Result<(), ServerError> {
405 if let Err(error) = health_shutdown {
406 if let Err(sequence_error) = &shutdown_sequence {
407 tracing::error!(
408 %sequence_error,
409 "shutdown sequence failed; the health endpoint shutdown failure takes the exit code"
410 );
411 }
412 return Err(error);
413 }
414 shutdown_sequence
415}
416
417fn start_cluster(
429 channel_cluster: &ChannelCluster,
430 cluster_config: &ClusterConfig,
431 readiness: &SharedReadinessState,
432) -> Result<ClusterHandle, ServerError> {
433 let resolver = channel_cluster
434 .resolver()
435 .cloned()
436 .ok_or_else(|| ServerError::ClusterJoin {
437 message: "clustering configured but channel supervisor has no distribution resolver"
438 .to_owned(),
439 })?;
440 let scheduler = channel_cluster.supervisor().scheduler();
441 let observer = Arc::clone(channel_cluster.observer());
442 let readiness = readiness.clone();
443 cluster::start(
444 &scheduler,
445 resolver,
446 cluster_config,
447 move |sync| {
448 let peers = Arc::clone(&observer);
451 if !sync.set_peer_listener(Arc::new(move |name, present| peers.peer(name, present))) {
452 tracing::error!("cluster peer listener was already installed");
453 }
454 if let Err(error) = observer.set_cluster(Arc::new(sync)) {
457 tracing::error!(%error, "cluster observer was not installed");
458 }
459 },
460 move || readiness.set_cluster_membership_established(true),
461 )
462}
463
464fn install_ephemeral_sweep_report(
465 services: &LiminalConnectionServices,
466 health: &crate::health::HealthServerHandle,
467) -> Result<(), ServerError> {
468 let report = services
469 .ephemeral_sweep_counts()
470 .ok_or_else(|| ServerError::HealthEndpoint {
471 message: "full services have no ephemeral root startup sweep report".to_owned(),
472 })?;
473 health.install_ephemeral_sweep_report(report)
474}
475
476#[cfg(test)]
477mod tests {
478 use std::net::SocketAddr;
479
480 use super::{ChannelCluster, ClusterConfig, SharedReadinessState, StartStages, start_cluster};
481 use crate::ServerError;
482 use crate::health::{ClusterReadiness, ReadinessCondition, ReadinessState, readiness_check};
483 use crate::server::connection::services::LiminalConnectionServices;
484
485 #[test]
488 fn start_stages_read_as_lengths_and_ends() {
489 let stages = StartStages {
490 began: std::time::Instant::now(),
491 marks: vec![("config", 3), ("health", 4), ("services", 2_104)],
492 };
493 assert_eq!(
494 stages.to_string(),
495 "config 3 (3), health 1 (4), services 2100 (2104)"
496 );
497 }
498
499 fn unclustered_channel_cluster() -> Result<ChannelCluster, ServerError> {
503 Ok(LiminalConnectionServices::empty()?
504 .channel_cluster()
505 .clone())
506 }
507
508 fn clustered_but_unmet_readiness() -> SharedReadinessState {
509 SharedReadinessState::new(ReadinessState::new(
510 true,
511 true,
512 ClusterReadiness::Configured {
513 membership_established: false,
514 },
515 ))
516 }
517
518 fn sample_cluster_config() -> Result<ClusterConfig, Box<dyn std::error::Error>> {
519 let listen_address: SocketAddr = "127.0.0.1:0".parse()?;
520 Ok(ClusterConfig {
521 node_name: "node-under-test@127.0.0.1".to_owned(),
522 listen_address,
523 seed_nodes: Vec::new(),
524 cookie: "runtime-test-cookie".to_owned(),
525 })
526 }
527
528 #[test]
529 fn failed_cluster_start_leaves_membership_unestablished()
530 -> Result<(), Box<dyn std::error::Error>> {
531 let readiness = clustered_but_unmet_readiness();
532 let channel_cluster = unclustered_channel_cluster()?;
533 let config = sample_cluster_config()?;
534
535 let result = start_cluster(&channel_cluster, &config, &readiness);
538 assert!(
539 result.is_err(),
540 "start_cluster must fail without a distribution resolver"
541 );
542
543 let status = readiness_check(&readiness.snapshot());
545 assert!(
546 !status.ready,
547 "readiness must remain not-ready after a failed start"
548 );
549 assert!(
550 status
551 .unmet_conditions
552 .contains(&ReadinessCondition::ClusterMembershipEstablished),
553 "cluster membership gate must stay unmet after a failed start"
554 );
555
556 Ok(())
557 }
558}