Skip to main content

pingora_core/server/
mod.rs

1// Copyright 2026 Cloudflare, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Server process and configuration management
16
17mod bootstrap_services;
18pub mod configuration;
19#[cfg(unix)]
20mod daemon;
21#[cfg(unix)]
22pub(crate) mod transfer_fd;
23
24use async_trait::async_trait;
25#[cfg(unix)]
26use daemon::daemonize;
27use daggy::NodeIndex;
28use log::{error, info, warn};
29use parking_lot::Mutex;
30#[cfg(all(feature = "dial9", feature = "dial9-worker-s3"))]
31pub use pingora_runtime::Dial9S3UploadOpts;
32use pingora_runtime::{BlockingPoolOpts, Runtime, RuntimeBuilder};
33#[cfg(feature = "dial9")]
34pub use pingora_runtime::{
35    Dial9RuntimeOpts, DEFAULT_DIAL9_MAX_FILE_SIZE, DEFAULT_DIAL9_MAX_TOTAL_SIZE,
36};
37pub use pingora_runtime::{RuntimeMetricsOpts, RuntimeOpts};
38use pingora_timeout::fast_timeout;
39#[cfg(feature = "sentry")]
40use sentry::ClientOptions;
41use std::sync::Arc;
42use std::thread;
43use std::time::{Instant, SystemTime};
44#[cfg(unix)]
45use tokio::signal::unix;
46use tokio::sync::{broadcast, watch};
47use tokio::time::{sleep, Duration};
48
49use crate::prelude::background_service;
50use crate::server::bootstrap_services::{Bootstrap, BootstrapService};
51use crate::services::{
52    DependencyGraph, ServiceHandle, ServiceReadyNotifier, ServiceReadyWatch, ServiceWithDependents,
53};
54use configuration::{Opt, ServerConf};
55use std::collections::HashMap;
56#[cfg(unix)]
57pub use transfer_fd::Fds;
58
59use pingora_error::{Error, ErrorType, Result};
60
61/* Time to wait before exiting the program.
62This is the graceful period for all existing sessions to finish */
63const EXIT_TIMEOUT: u64 = 60 * 5;
64/* Time to wait before shutting down listening sockets.
65This is the graceful period for the new service to get ready */
66const CLOSE_TIMEOUT: u64 = 5;
67
68enum ShutdownType {
69    Graceful,
70    Quick,
71}
72
73/// Internal wrapper for services with dependency metadata.
74pub(crate) struct ServiceWrapper {
75    ready_notifier: Option<ServiceReadyNotifier>,
76    service: Box<dyn ServiceWithDependents>,
77    service_handle: ServiceHandle,
78}
79
80/// The execution phase the server is currently in.
81#[derive(Clone, Debug)]
82#[non_exhaustive]
83pub enum ExecutionPhase {
84    /// The server was created, but has not started yet.
85    Setup,
86
87    /// Services are being prepared.
88    ///
89    /// During graceful upgrades this phase acquires the listening FDs from the old process.
90    Bootstrap,
91
92    /// Bootstrap has finished, listening FDs have been transferred.
93    BootstrapComplete,
94
95    /// The server is running and is listening for shutdown signals.
96    Running,
97
98    /// A QUIT signal was received, indicating that a new process wants to take over.
99    ///
100    /// The server is trying to send the fds to the new process over a Unix socket.
101    GracefulUpgradeTransferringFds,
102
103    /// FDs have been sent to the new process.
104    /// Waiting a fixed amount of time to allow the new process to take the sockets.
105    GracefulUpgradeCloseTimeout,
106
107    /// A TERM signal was received, indicating that the server should shut down gracefully.
108    GracefulTerminate,
109
110    /// The server is shutting down.
111    ShutdownStarted,
112
113    /// Waiting for the configured grace period to end before shutting down.
114    ShutdownGracePeriod,
115
116    /// Wait for runtimes to finish.
117    ShutdownRuntimes,
118
119    /// The server has stopped.
120    Terminated,
121}
122
123/// The receiver for server's shutdown event. The value will turn to true once the server starts
124/// to shutdown
125pub type ShutdownWatch = watch::Receiver<bool>;
126#[cfg(unix)]
127pub type ListenFds = Arc<Mutex<Fds>>;
128
129/// The type of shutdown process that has been requested.
130#[derive(Debug)]
131pub enum ShutdownSignal {
132    /// Send file descriptors to the new process before starting runtime shutdown with
133    /// [ServerConf::graceful_shutdown_timeout_seconds] timeout.
134    GracefulUpgrade,
135    /// Wait for [ServerConf::grace_period_seconds] before starting runtime shutdown with
136    /// [ServerConf::graceful_shutdown_timeout_seconds] timeout.
137    GracefulTerminate,
138    /// Shutdown with no timeout for runtime shutdown.
139    FastShutdown,
140}
141
142/// Watcher of a shutdown signal, e.g., [UnixShutdownSignalWatch] for Unix-like
143/// platforms.
144#[async_trait]
145pub trait ShutdownSignalWatch {
146    /// Returns the desired shutdown type once one has been requested.
147    async fn recv(&self) -> ShutdownSignal;
148}
149
150/// A Unix shutdown watcher that awaits for Unix signals.
151///
152/// - `SIGQUIT`: graceful upgrade
153/// - `SIGTERM`: graceful terminate
154/// - `SIGINT`: fast shutdown
155#[cfg(unix)]
156pub struct UnixShutdownSignalWatch;
157
158#[cfg(unix)]
159#[async_trait]
160impl ShutdownSignalWatch for UnixShutdownSignalWatch {
161    async fn recv(&self) -> ShutdownSignal {
162        let mut graceful_upgrade_signal = unix::signal(unix::SignalKind::quit()).unwrap();
163        let mut graceful_terminate_signal = unix::signal(unix::SignalKind::terminate()).unwrap();
164        let mut fast_shutdown_signal = unix::signal(unix::SignalKind::interrupt()).unwrap();
165
166        tokio::select! {
167            _ = graceful_upgrade_signal.recv() => {
168                ShutdownSignal::GracefulUpgrade
169            },
170            _ = graceful_terminate_signal.recv() => {
171                ShutdownSignal::GracefulTerminate
172            },
173            _ = fast_shutdown_signal.recv() => {
174                ShutdownSignal::FastShutdown
175            },
176        }
177    }
178}
179
180/// Arguments to configure running of the pingora server.
181pub struct RunArgs {
182    /// Signal for initating shutdown
183    #[cfg(unix)]
184    pub shutdown_signal: Box<dyn ShutdownSignalWatch>,
185}
186
187impl Default for RunArgs {
188    #[cfg(unix)]
189    fn default() -> Self {
190        Self {
191            shutdown_signal: Box::new(UnixShutdownSignalWatch),
192        }
193    }
194
195    #[cfg(windows)]
196    fn default() -> Self {
197        Self {}
198    }
199}
200
201/// The server object
202///
203/// This object represents an entire pingora server process which may have multiple independent
204/// services (see [crate::services]). The server object handles signals, reading configuration,
205/// zero downtime upgrade and error reporting.
206pub struct Server {
207    services: HashMap<NodeIndex, ServiceWrapper>,
208    shutdown_watch: watch::Sender<bool>,
209    // TODO: we many want to drop this copy to let sender call closed()
210    shutdown_recv: ShutdownWatch,
211
212    /// Tracks the execution phase of the server during upgrades and graceful shutdowns.
213    ///
214    /// Users can subscribe to the phase with [`Self::watch_execution_phase()`].
215    execution_phase_watch: broadcast::Sender<ExecutionPhase>,
216
217    /// Specification of service level dependencies
218    dependencies: Arc<Mutex<DependencyGraph>>,
219
220    /// Service initialization
221    bootstrap: Arc<Mutex<Bootstrap>>,
222
223    /// The parsed server configuration
224    pub configuration: Arc<ServerConf>,
225    /// The parser command line options
226    pub options: Option<Opt>,
227}
228
229// TODO: delete the pid when exit
230
231impl Server {
232    /// Acquire a receiver for the server's execution phase.
233    ///
234    /// The receiver will produce values for each transition.
235    pub fn watch_execution_phase(&self) -> broadcast::Receiver<ExecutionPhase> {
236        self.execution_phase_watch.subscribe()
237    }
238
239    #[cfg(unix)]
240    async fn main_loop(&self, run_args: RunArgs) -> ShutdownType {
241        // waiting for exit signal
242
243        self.execution_phase_watch
244            .send(ExecutionPhase::Running)
245            .ok();
246
247        match run_args.shutdown_signal.recv().await {
248            ShutdownSignal::FastShutdown => {
249                info!("SIGINT received, exiting");
250                ShutdownType::Quick
251            }
252            ShutdownSignal::GracefulTerminate => {
253                // we receive a graceful terminate, all instances are instructed to stop
254                info!("SIGTERM received, gracefully exiting");
255                // graceful shutdown if there are listening sockets
256                info!("Broadcasting graceful shutdown");
257                match self.shutdown_watch.send(true) {
258                    Ok(_) => {
259                        info!("Graceful shutdown started!");
260                    }
261                    Err(e) => {
262                        error!("Graceful shutdown broadcast failed: {e}");
263                    }
264                }
265                info!("Broadcast graceful shutdown complete");
266
267                self.execution_phase_watch
268                    .send(ExecutionPhase::GracefulTerminate)
269                    .ok();
270
271                ShutdownType::Graceful
272            }
273            ShutdownSignal::GracefulUpgrade => {
274                // TODO: still need to select! on signals in case a fast shutdown is needed
275                // aka: move below to another task and only kick it off here
276                info!("SIGQUIT received, sending socks and gracefully exiting");
277
278                self.execution_phase_watch
279                    .send(ExecutionPhase::GracefulUpgradeTransferringFds)
280                    .ok();
281
282                let sent_fds = {
283                    let fds = self.listen_fds();
284                    let fds = fds.lock();
285                    if fds.is_empty() {
286                        info!("No socks to send, shutting down.");
287                        false
288                    } else {
289                        info!("Trying to send socks");
290                        match fds.send_to_sock(self.configuration.as_ref().upgrade_sock.as_str()) {
291                            Ok(_) => {
292                                info!("listener sockets sent");
293                            }
294                            Err(e) => {
295                                error!("Unable to send listener sockets to new process: {e}");
296                                #[cfg(all(not(debug_assertions), feature = "sentry"))]
297                                sentry::capture_error(&e);
298                            }
299                        }
300                        true
301                    }
302                };
303                if sent_fds {
304                    self.execution_phase_watch
305                        .send(ExecutionPhase::GracefulUpgradeCloseTimeout)
306                        .ok();
307                    sleep(Duration::from_secs(CLOSE_TIMEOUT)).await;
308                }
309                info!("Broadcasting graceful shutdown");
310                // gracefully exiting
311                match self.shutdown_watch.send(true) {
312                    Ok(_) => {
313                        info!("Graceful shutdown started!");
314                    }
315                    Err(e) => {
316                        error!("Graceful shutdown broadcast failed: {e}");
317                        // switch to fast shutdown
318                        return ShutdownType::Graceful;
319                    }
320                }
321                info!("Broadcast graceful shutdown complete");
322                ShutdownType::Graceful
323            }
324        }
325    }
326
327    #[cfg(windows)]
328    async fn main_loop(&self, _run_args: RunArgs) -> ShutdownType {
329        // waiting for exit signal
330
331        self.execution_phase_watch
332            .send(ExecutionPhase::Running)
333            .ok();
334
335        match tokio::signal::ctrl_c().await {
336            Ok(()) => {
337                info!("Ctrl+C received, gracefully exiting");
338                // graceful shutdown if there are listening sockets
339                info!("Broadcasting graceful shutdown");
340                match self.shutdown_watch.send(true) {
341                    Ok(_) => {
342                        info!("Graceful shutdown started!");
343                    }
344                    Err(e) => {
345                        error!("Graceful shutdown broadcast failed: {e}");
346                    }
347                }
348                info!("Broadcast graceful shutdown complete");
349
350                self.execution_phase_watch
351                    .send(ExecutionPhase::GracefulTerminate)
352                    .ok();
353
354                ShutdownType::Graceful
355            }
356            Err(e) => {
357                error!("Unable to listen for shutdown signal: {}", e);
358                ShutdownType::Quick
359            }
360        }
361    }
362
363    #[cfg(feature = "sentry")]
364    #[cfg_attr(docsrs, doc(cfg(feature = "sentry")))]
365    /// The Sentry ClientOptions.
366    ///
367    /// Panics and other events sentry captures will be sent to this DSN **only in release mode**
368    pub fn set_sentry_config(&mut self, sentry_config: ClientOptions) {
369        self.bootstrap.lock().set_sentry_config(Some(sentry_config));
370    }
371
372    /// Get the configured file descriptors for listening
373    #[cfg(unix)]
374    fn listen_fds(&self) -> ListenFds {
375        self.bootstrap.lock().get_fds()
376    }
377
378    /// Collect the listening bind addresses across all registered services.
379    #[cfg(unix)]
380    fn collect_listen_addresses(&self) -> Option<std::collections::HashSet<String>> {
381        self.services
382            .values()
383            .try_fold(std::collections::HashSet::new(), |mut addrs, wrapper| {
384                addrs.extend(wrapper.service.listen_addresses()?);
385                Some(addrs)
386            })
387    }
388
389    #[allow(clippy::too_many_arguments)]
390    fn run_service(
391        mut service: Box<dyn ServiceWithDependents>,
392        #[cfg(unix)] fds: ListenFds,
393        shutdown: ShutdownWatch,
394        threads: usize,
395        work_stealing: bool,
396        listeners_per_fd: usize,
397        ready_notifier: ServiceReadyNotifier,
398        dependency_watches: Vec<ServiceReadyWatch>,
399        blocking_opts: BlockingPoolOpts,
400        runtime_opts: RuntimeOpts,
401    ) -> Runtime
402// NOTE: we need to keep the runtime outside async since
403        // otherwise the runtime will be dropped.
404    {
405        let service_runtime = Server::create_runtime(
406            service.name(),
407            threads,
408            work_stealing,
409            blocking_opts,
410            runtime_opts,
411        );
412        let service_name = service.name().to_string();
413        service_runtime.get_handle().spawn(async move {
414            // Wait for all dependencies to be ready
415            let mut time_waited_opt: Option<Duration> = None;
416            for mut watch in dependency_watches {
417                let start = SystemTime::now();
418
419                if watch.wait_for(|&ready| ready).await.is_err() {
420                    error!(
421                        "Service '{}' dependency channel closed before ready",
422                        service_name
423                    );
424                }
425
426                *time_waited_opt.get_or_insert_default() += start.elapsed().unwrap_or_default()
427            }
428
429            if let Some(time_waited) = time_waited_opt {
430                service.on_startup_delay(time_waited);
431            }
432
433            // Start the actual service, passing the ready notifier
434            service
435                .start_service(
436                    #[cfg(unix)]
437                    Some(fds),
438                    shutdown,
439                    listeners_per_fd,
440                    ready_notifier,
441                )
442                .await;
443            info!("service '{}' exited.", service_name);
444        });
445        service_runtime
446    }
447
448    /// Create a new [`Server`], using the [`Opt`] and [`ServerConf`] values provided
449    ///
450    /// This method is intended for pingora frontends that are NOT using the built-in
451    /// command line and configuration file parsing, and are instead using their own.
452    ///
453    /// If a configuration file path is provided as part of `opt`, it will be ignored
454    /// and a warning will be logged.
455    pub fn new_with_opt_and_conf(raw_opt: impl Into<Option<Opt>>, mut conf: ServerConf) -> Server {
456        let opt = raw_opt.into();
457        if let Some(opts) = &opt {
458            if let Some(c) = opts.conf.as_ref() {
459                warn!("Ignoring command line argument using '{c}' as configuration, and using provided configuration instead.");
460            }
461            conf.merge_with_opt(opts);
462        }
463
464        let (tx, rx) = watch::channel(false);
465
466        let execution_phase_watch = broadcast::channel(100).0;
467        let bootstrap = Arc::new(Mutex::new(Bootstrap::new(
468            &opt,
469            &conf,
470            &execution_phase_watch,
471        )));
472
473        Server {
474            services: Default::default(),
475            shutdown_watch: tx,
476            shutdown_recv: rx,
477            execution_phase_watch,
478            configuration: Arc::new(conf),
479            options: opt,
480            dependencies: Arc::new(Mutex::new(DependencyGraph::new())),
481            bootstrap,
482        }
483    }
484
485    /// Create a new [`Server`].
486    ///
487    /// Only one [`Server`] needs to be created for a process. A [`Server`] can hold multiple
488    /// independent services.
489    ///
490    /// Command line options can either be passed by parsing the command line arguments via
491    /// `Opt::parse_args()`, or be generated by other means.
492    pub fn new(opt: impl Into<Option<Opt>>) -> Result<Server> {
493        let opt = opt.into();
494        let (tx, rx) = watch::channel(false);
495
496        let execution_phase_watch = broadcast::channel(100).0;
497        let conf = if let Some(opt) = opt.as_ref() {
498            opt.conf.as_ref().map_or_else(
499                || {
500                    // options, no conf, generated
501                    ServerConf::new_with_opt_override(opt).ok_or_else(|| {
502                        Error::explain(ErrorType::ReadError, "Conf generation failed")
503                    })
504                },
505                |_| {
506                    // options and conf loaded
507                    ServerConf::load_yaml_with_opt_override(opt)
508                },
509            )
510        } else {
511            ServerConf::new()
512                .ok_or_else(|| Error::explain(ErrorType::ReadError, "Conf generation failed"))
513        }?;
514
515        let bootstrap = Arc::new(Mutex::new(Bootstrap::new(
516            &opt,
517            &conf,
518            &execution_phase_watch,
519        )));
520
521        Ok(Server {
522            services: Default::default(),
523            shutdown_watch: tx,
524            shutdown_recv: rx,
525            execution_phase_watch,
526            configuration: Arc::new(conf),
527            options: opt,
528            dependencies: Arc::new(Mutex::new(DependencyGraph::new())),
529            bootstrap,
530        })
531    }
532
533    /// Add a service to this server.
534    ///
535    /// Returns a [`ServiceHandle`] that can be used to declare dependencies.
536    ///
537    /// # Example
538    ///
539    /// ```rust,ignore
540    /// let db_id = server.add_service(database_service);
541    /// let api_id = server.add_service(api_service);
542    ///
543    /// // Declare that API depends on database
544    /// api_id.add_dependency(&db_id);
545    /// ```
546    pub fn add_service(&mut self, service: impl ServiceWithDependents + 'static) -> ServiceHandle {
547        self.add_boxed_service(Box::new(service))
548    }
549
550    /// Add a pre-boxed service to this server.
551    ///
552    /// Returns a [`ServiceHandle`] that can be used to declare dependencies.
553    ///
554    /// # Example
555    ///
556    /// ```rust,ignore
557    /// let db_id = server.add_service(database_service);
558    /// let api_id = server.add_service(api_service);
559    ///
560    /// // Declare that API depends on database
561    /// api_id.add_dependency(&db_id);
562    /// ```
563    pub fn add_boxed_service(
564        &mut self,
565        service_box: Box<dyn ServiceWithDependents>,
566    ) -> ServiceHandle {
567        let name = service_box.name().to_string();
568
569        // Create a readiness notifier for this service
570        let (tx, rx) = watch::channel(false);
571
572        let id = self.dependencies.lock().add_node(name.clone(), rx.clone());
573
574        let service_handle = ServiceHandle::new(id, name, rx, &self.dependencies);
575
576        let wrapper = ServiceWrapper {
577            ready_notifier: Some(ServiceReadyNotifier::new(tx)),
578            service: service_box,
579            service_handle: service_handle.clone(),
580        };
581
582        self.services.insert(id, wrapper);
583
584        service_handle
585    }
586
587    /// Similar to [`Self::add_service()`], but take a list of services.
588    ///
589    /// Returns a `Vec<ServiceHandle>` for all added services.
590    pub fn add_services(
591        &mut self,
592        services: Vec<Box<dyn ServiceWithDependents>>,
593    ) -> Vec<ServiceHandle> {
594        services
595            .into_iter()
596            .map(|service| self.add_boxed_service(service))
597            .collect()
598    }
599
600    /// Prepare the server to start
601    ///
602    /// When trying to zero downtime upgrade from an older version of the server which is already
603    /// running, this function will try to get all its listening sockets in order to take them over.
604    pub fn bootstrap(&mut self) {
605        self.bootstrap.lock().bootstrap();
606    }
607
608    /// Create a service that will run to prepare the service to start
609    ///
610    /// The created service will handle the zero-downtime upgrade from an older version of the server
611    /// to this one. It will try to get all its listening sockets in order to take them over.
612    pub fn bootstrap_as_a_service(&mut self) -> ServiceHandle {
613        let bootstrap_service =
614            background_service("Bootstrap Service", BootstrapService::new(&self.bootstrap));
615
616        self.add_service(bootstrap_service)
617    }
618
619    /// Start the server using [Self::run] and default [RunArgs].
620    ///
621    /// This function will block forever until the server needs to quit. So this would be the last
622    /// function to call for this object.
623    ///
624    /// Note: this function may fork the process for daemonization, so any additional threads created
625    /// before this function will be lost to any service logic once this function is called.
626    pub fn run_forever(self) -> ! {
627        self.run(RunArgs::default());
628
629        std::process::exit(0)
630    }
631
632    /// Run the server until execution finished.
633    ///
634    /// This function will run until the server has been instructed to shut down
635    /// through a signal, and will then wait for all services to finish and
636    /// runtimes to exit.
637    ///
638    /// Note: if daemonization is enabled in the config, this function will
639    /// never return.
640    /// Instead it will either start the daemon process and exit, or panic
641    /// if daemonization fails.
642    pub fn run(mut self, run_args: RunArgs) {
643        info!("Server starting");
644
645        let conf = self.configuration.as_ref();
646
647        #[cfg(unix)]
648        if conf.daemon {
649            info!("Daemonizing the server");
650            fast_timeout::pause_for_fork();
651            let daemonize_result = daemonize(&self.configuration);
652            fast_timeout::unpause();
653            // If daemon_wait_for_ready is enabled, pass the parent PID to bootstrap so it
654            // can send SIGUSR1 to the parent after bootstrap completes.
655            if let Some(pid) = daemonize_result.notify_parent_pid {
656                self.bootstrap.lock().set_notify_parent_pid(pid);
657            }
658        }
659
660        #[cfg(windows)]
661        if conf.daemon {
662            panic!("Daemonizing under windows is not supported");
663        }
664
665        let blocking_opts = BlockingPoolOpts {
666            max_threads: conf.max_blocking_threads,
667            thread_keep_alive: conf.blocking_threads_ttl_seconds.map(Duration::from_secs),
668        };
669        let runtime_opts = conf.runtime_opts();
670        if conf.runtime_enable_alt_timer && !conf.work_stealing {
671            warn!("runtime_enable_alt_timer is ignored when work_stealing is disabled");
672        }
673        // This global timeout threshold is intended to be configured once during server startup,
674        // before service runtimes begin creating timeout futures.
675        fast_timeout::set_fast_timeout_to_tokio_threshold(
676            conf.fast_timeout_to_tokio_threshold_seconds
677                .map(Duration::from_secs),
678        );
679
680        // Initialize (or re-initialize) sentry and persist the guard for
681        // the lifetime of the server. When daemonizing, the transport
682        // thread spawned by any earlier `sentry::init` during
683        // `bootstrap()` is lost after `fork()`, so a fresh init in the
684        // child process is required. In non-daemon mode this is the
685        // authoritative initialization that keeps sentry active.
686        #[cfg(feature = "sentry")]
687        self.bootstrap.lock().start_sentry();
688
689        // Holds tuples of runtimes and their service name.
690        let mut runtimes: Vec<(Runtime, String)> = Vec::new();
691
692        // Set this before the bootstrap service loads inherited fds.
693        #[cfg(unix)]
694        if let Some(expected) = self.collect_listen_addresses() {
695            self.bootstrap.lock().set_expected_listen_addrs(expected);
696        }
697
698        // Get services in topological order (dependencies first)
699        let startup_order = match self.dependencies.lock().topological_sort() {
700            Ok(order) => order,
701            Err(e) => {
702                error!("Failed to determine service startup order: {}", e);
703                std::process::exit(1);
704            }
705        };
706
707        // Log service names in startup order
708        let service_names: Vec<String> = startup_order
709            .iter()
710            .map(|(_, service)| service.name.clone())
711            .collect();
712        info!("Starting services in dependency order: {:?}", service_names);
713
714        // Start services in dependency order
715        for (service_id, service) in startup_order {
716            let mut wrapper = match self.services.remove(&service_id) {
717                Some(w) => w,
718                None => {
719                    warn!(
720                        "Service ID {:?}-{} in startup order but not found",
721                        service_id, service.name
722                    );
723                    continue;
724                }
725            };
726
727            let threads = wrapper.service.threads().unwrap_or(conf.threads);
728            let name = wrapper.service.name().to_string();
729            let service_runtime_opts = wrapper
730                .service
731                .runtime_opts_override(&runtime_opts)
732                .unwrap_or_else(|| runtime_opts.clone());
733
734            // Extract dependency watches from the ServiceHandle
735            let dependencies = self
736                .dependencies
737                .lock()
738                .get_dependencies(wrapper.service_handle.id);
739
740            // Get the readiness notifier for this service by taking it from the Option.
741            // Since service_id is the index, we can directly access it.
742            // We take() the notifier, leaving None in its place.
743            let ready_notifier = wrapper
744                .ready_notifier
745                .take()
746                .expect("Service notifier should exist");
747
748            if !dependencies.is_empty() {
749                info!(
750                    "Service '{name}' will wait for dependencies: {:?}",
751                    dependencies.iter().map(|s| &s.name).collect::<Vec<_>>()
752                );
753            } else {
754                info!("Starting service: {}", name);
755            }
756
757            let dependency_watches = dependencies
758                .iter()
759                .map(|s| s.ready_watch.clone())
760                .collect::<Vec<_>>();
761
762            let runtime = Server::run_service(
763                wrapper.service,
764                #[cfg(unix)]
765                self.listen_fds(),
766                self.shutdown_recv.clone(),
767                threads,
768                conf.work_stealing,
769                self.configuration.listener_tasks_per_fd,
770                ready_notifier,
771                dependency_watches,
772                blocking_opts.clone(),
773                service_runtime_opts,
774            );
775            runtimes.push((runtime, name));
776        }
777
778        // blocked on main loop so that it runs forever
779        // Only work steal runtime can use block_on()
780        let server_runtime = Server::create_runtime(
781            "Server",
782            1,
783            true,
784            BlockingPoolOpts::default(),
785            RuntimeOpts::default(),
786        );
787        #[cfg(unix)]
788        let shutdown_type = server_runtime
789            .get_handle()
790            .block_on(self.main_loop(run_args));
791        #[cfg(windows)]
792        let shutdown_type = server_runtime
793            .get_handle()
794            .block_on(self.main_loop(run_args));
795
796        self.execution_phase_watch
797            .send(ExecutionPhase::ShutdownStarted)
798            .ok();
799
800        if matches!(shutdown_type, ShutdownType::Graceful) {
801            self.execution_phase_watch
802                .send(ExecutionPhase::ShutdownGracePeriod)
803                .ok();
804
805            let exit_timeout = self
806                .configuration
807                .as_ref()
808                .grace_period_seconds
809                .unwrap_or(EXIT_TIMEOUT);
810            info!("Graceful shutdown: grace period {}s starts", exit_timeout);
811            thread::sleep(Duration::from_secs(exit_timeout));
812            info!("Graceful shutdown: grace period ends");
813        }
814
815        // Give tokio runtimes time to exit
816        let shutdown_timeout = match shutdown_type {
817            ShutdownType::Quick => Duration::from_secs(0),
818            ShutdownType::Graceful => Duration::from_secs(
819                self.configuration
820                    .as_ref()
821                    .graceful_shutdown_timeout_seconds
822                    .unwrap_or(5),
823            ),
824        };
825
826        self.execution_phase_watch
827            .send(ExecutionPhase::ShutdownRuntimes)
828            .ok();
829
830        let shutdowns: Vec<_> = runtimes
831            .into_iter()
832            .map(|(rt, name)| {
833                info!("Waiting for runtimes to exit!");
834                let join = thread::spawn(move || {
835                    let start = Instant::now();
836                    rt.shutdown_timeout(shutdown_timeout);
837                    start.elapsed()
838                });
839                (join, name)
840            })
841            .collect();
842        for (shutdown, name) in shutdowns {
843            info!("Waiting for service runtime {name} to exit");
844            match shutdown.join() {
845                Ok(elapsed) if !shutdown_timeout.is_zero() && elapsed >= shutdown_timeout => {
846                    warn!("Service runtime {name} did not exit within {shutdown_timeout:?}")
847                }
848                Ok(elapsed) => info!("Service runtime {name} exited after {elapsed:?}"),
849                Err(e) => error!("Failed to shutdown service runtime {name}: {e:?}"),
850            }
851        }
852        info!("All runtimes exited, exiting now");
853
854        self.execution_phase_watch
855            .send(ExecutionPhase::Terminated)
856            .ok();
857    }
858
859    fn create_runtime(
860        name: &str,
861        threads: usize,
862        work_steal: bool,
863        blocking_opts: BlockingPoolOpts,
864        runtime_opts: RuntimeOpts,
865    ) -> Runtime {
866        RuntimeBuilder::new(threads, name)
867            .work_steal(work_steal)
868            .blocking_pool_opts(blocking_opts)
869            .runtime_opts(runtime_opts)
870            .build()
871    }
872}
873
874#[cfg(all(test, unix))]
875mod listen_address_tests {
876    use super::*;
877
878    struct UnknownService;
879
880    #[async_trait]
881    impl crate::services::Service for UnknownService {
882        fn name(&self) -> &str {
883            "unknown"
884        }
885    }
886
887    #[test]
888    fn unknown_service_disables_inherited_fd_cleanup() {
889        let mut server = Server::new_with_opt_and_conf(None, ServerConf::default());
890        server.bootstrap_as_a_service();
891        assert_eq!(server.collect_listen_addresses(), Some(Default::default()));
892
893        server.add_service(UnknownService);
894        assert_eq!(server.collect_listen_addresses(), None);
895    }
896}