Skip to main content

actix_server/
builder.rs

1use std::{future::Future, io, num::NonZeroUsize, time::Duration};
2
3use actix_rt::net::TcpStream;
4use futures_core::future::BoxFuture;
5use tokio::sync::{
6    mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
7    watch,
8};
9
10use crate::{
11    availability::MAX_WORKERS,
12    server::ServerCommand,
13    service::{InternalServiceFactory, ServerServiceFactory, StreamNewService},
14    socket::{create_mio_tcp_listener, MioListener, MioTcpListener, StdTcpListener, ToSocketAddrs},
15    worker::ServerWorkerConfig,
16    Server,
17};
18
19/// Multipath TCP (MPTCP) preference.
20///
21/// Currently only useful on Linux.
22///
23#[cfg_attr(target_os = "linux", doc = "Also see [`ServerBuilder::mptcp()`].")]
24#[derive(Debug, Clone)]
25pub enum MpTcp {
26    /// MPTCP will not be used when binding sockets.
27    Disabled,
28
29    /// MPTCP will be attempted when binding sockets. If errors occur, regular TCP will be
30    /// attempted, too.
31    TcpFallback,
32
33    /// MPTCP will be used when binding sockets (with no fallback).
34    NoFallback,
35}
36
37/// [Server] builder.
38pub struct ServerBuilder {
39    pub(crate) threads: usize,
40    pub(crate) token: usize,
41    pub(crate) backlog: u32,
42    pub(crate) factories: Vec<Box<dyn InternalServiceFactory>>,
43    pub(crate) sockets: Vec<(usize, String, MioListener)>,
44    pub(crate) mptcp: MpTcp,
45    pub(crate) exit: bool,
46    pub(crate) listen_os_signals: bool,
47    pub(crate) shutdown_signal: Option<BoxFuture<'static, ()>>,
48    pub(crate) graceful_shutdown_tx: watch::Sender<()>,
49    pub(crate) cmd_tx: UnboundedSender<ServerCommand>,
50    pub(crate) cmd_rx: UnboundedReceiver<ServerCommand>,
51    pub(crate) worker_config: ServerWorkerConfig,
52}
53
54impl Default for ServerBuilder {
55    fn default() -> Self {
56        Self::new()
57    }
58}
59
60impl ServerBuilder {
61    /// Create new Server builder instance
62    pub fn new() -> ServerBuilder {
63        Self::with_default_workers(
64            std::thread::available_parallelism().map_or(2, NonZeroUsize::get),
65        )
66    }
67
68    fn with_default_workers(threads: usize) -> ServerBuilder {
69        let (cmd_tx, cmd_rx) = unbounded_channel();
70        let (graceful_shutdown_tx, _) = watch::channel(());
71
72        ServerBuilder {
73            threads: threads.min(MAX_WORKERS),
74            token: 0,
75            factories: Vec::new(),
76            sockets: Vec::new(),
77            backlog: 2048,
78            mptcp: MpTcp::Disabled,
79            exit: false,
80            listen_os_signals: true,
81            shutdown_signal: None,
82            graceful_shutdown_tx,
83            cmd_tx,
84            cmd_rx,
85            worker_config: ServerWorkerConfig::default(),
86        }
87    }
88
89    /// Returns a signal that resolves when this server starts a graceful shutdown.
90    ///
91    /// The signal cannot stop the server. It only propagates a shutdown that was started by an OS
92    /// signal, [`ServerHandle::stop`](crate::ServerHandle::stop), or [`Self::shutdown_signal`].
93    pub fn graceful_shutdown_signal(&self) -> crate::GracefulShutdownSignal {
94        crate::GracefulShutdownSignal::new(self.graceful_shutdown_tx.subscribe())
95    }
96
97    /// Sets number of workers to start.
98    ///
99    /// See [`bind()`](Self::bind()) for more details on how worker count affects the number of
100    /// server factory instantiations.
101    ///
102    /// The default worker count is determined by [`std::thread::available_parallelism()`], capped
103    /// at 512. See its documentation for how the available parallelism is determined.
104    ///
105    /// `num` must be in the range 1–512.
106    ///
107    /// # Panics
108    ///
109    /// Panics if `num` is 0 or greater than 512.
110    pub fn workers(mut self, num: usize) -> Self {
111        assert_ne!(num, 0, "workers must be greater than 0");
112        assert!(num <= MAX_WORKERS, "workers must not exceed {MAX_WORKERS}");
113        self.threads = num;
114        self
115    }
116
117    /// Set max number of threads for each worker's blocking task thread pool.
118    ///
119    /// One thread pool is set up **per worker**; not shared across workers.
120    ///
121    /// # Examples:
122    /// ```
123    /// # use actix_server::ServerBuilder;
124    /// let builder = ServerBuilder::new()
125    ///     .workers(4) // server has 4 worker thread.
126    ///     .worker_max_blocking_threads(4); // every worker has 4 max blocking threads.
127    /// ```
128    ///
129    /// See [tokio::runtime::Builder::max_blocking_threads] for behavior reference.
130    pub fn worker_max_blocking_threads(mut self, num: usize) -> Self {
131        self.worker_config.max_blocking_threads(num);
132        self
133    }
134
135    /// Set the maximum number of pending connections.
136    ///
137    /// This refers to the number of clients that can be waiting to be served. Exceeding this number
138    /// results in the client getting an error when attempting to connect. It should only affect
139    /// servers under significant load.
140    ///
141    /// Generally set in the 64-2048 range. Default value is 2048.
142    ///
143    /// This method should be called before `bind()` method call.
144    pub fn backlog(mut self, num: u32) -> Self {
145        self.backlog = num;
146        self
147    }
148
149    /// Sets MultiPath TCP (MPTCP) preference on bound sockets.
150    ///
151    /// Multipath TCP (MPTCP) builds on top of TCP to improve connection redundancy and performance
152    /// by sharing a network data stream across multiple underlying TCP sessions. See [mptcp.dev]
153    /// for more info about MPTCP itself.
154    ///
155    /// MPTCP is available on Linux kernel version 5.6 and higher. In addition, you'll also need to
156    /// ensure the kernel option is enabled using `sysctl net.mptcp.enabled=1`.
157    ///
158    /// This method will have no effect if called after a `bind()`.
159    ///
160    /// [mptcp.dev]: https://www.mptcp.dev
161    #[cfg(target_os = "linux")]
162    pub fn mptcp(mut self, mptcp_enabled: MpTcp) -> Self {
163        self.mptcp = mptcp_enabled;
164        self
165    }
166
167    /// Sets the maximum per-worker number of concurrent connections.
168    ///
169    /// All socket listeners will stop accepting connections when this limit is reached for
170    /// each worker.
171    ///
172    /// By default max connections is set to a 25k per worker.
173    pub fn max_concurrent_connections(mut self, num: usize) -> Self {
174        self.worker_config.max_concurrent_connections(num);
175        self
176    }
177
178    #[doc(hidden)]
179    #[deprecated(since = "2.0.0", note = "Renamed to `max_concurrent_connections`.")]
180    pub fn maxconn(self, num: usize) -> Self {
181        self.max_concurrent_connections(num)
182    }
183
184    /// Sets flag to stop Actix `System` after server shutdown.
185    ///
186    /// This has no effect when server is running in a Tokio-only runtime.
187    pub fn system_exit(mut self) -> Self {
188        self.exit = true;
189        self
190    }
191
192    /// Disables OS signal handling.
193    pub fn disable_signals(mut self) -> Self {
194        self.listen_os_signals = false;
195        self
196    }
197
198    /// Specify shutdown signal from a future.
199    ///
200    /// Using this method will prevent OS signal handlers being set up.
201    ///
202    /// Typically, a `CancellationToken` will be used, but any future _can_ be.
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// # use std::io;
208    /// # use tokio::net::TcpStream;
209    /// # use actix_server::Server;
210    /// # async fn run() -> io::Result<()> {
211    /// use actix_service::fn_service;
212    /// use tokio_util::sync::CancellationToken;
213    ///
214    /// let stop_signal = CancellationToken::new();
215    ///
216    /// Server::build()
217    ///     .bind("shutdown-signal", "127.0.0.1:12345", || {
218    ///         fn_service(|_stream: TcpStream| async { Ok::<_, io::Error>(()) })
219    ///     })?
220    ///     .shutdown_signal(stop_signal.cancelled_owned())
221    ///     .run()
222    ///     .await
223    /// # }
224    /// ```
225    pub fn shutdown_signal<Fut>(mut self, shutdown_signal: Fut) -> Self
226    where
227        Fut: Future<Output = ()> + Send + 'static,
228    {
229        self.shutdown_signal = Some(Box::pin(shutdown_signal));
230        self
231    }
232
233    /// Timeout for graceful workers shutdown in seconds.
234    ///
235    /// After receiving a stop signal, workers have this much time to finish serving requests.
236    /// Workers still alive after the timeout are force dropped.
237    ///
238    /// By default shutdown timeout sets to 30 seconds.
239    pub fn shutdown_timeout(mut self, sec: u64) -> Self {
240        self.worker_config
241            .shutdown_timeout(Duration::from_secs(sec));
242        self
243    }
244
245    /// Adds new service to the server.
246    ///
247    /// Note that, if a DNS lookup is required, resolving hostnames is a blocking operation.
248    ///
249    /// # Worker Count
250    ///
251    /// The `factory` will be instantiated multiple times in most scenarios. The number of
252    /// instantiations is number of [`workers`](Self::workers()) × number of sockets resolved by
253    /// `addrs`.
254    ///
255    /// For example, if you've manually set [`workers`](Self::workers()) to 2, and use `127.0.0.1`
256    /// as the bind `addrs`, then `factory` will be instantiated twice. However, using `localhost`
257    /// as the bind `addrs` can often resolve to both `127.0.0.1` (IPv4) _and_ `::1` (IPv6), causing
258    /// the `factory` to be instantiated 4 times (2 workers × 2 bind addresses).
259    ///
260    /// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple
261    /// the number of instantiations in a similar way.
262    ///
263    /// # Errors
264    ///
265    /// Returns an `io::Error` if:
266    /// - `addrs` cannot be resolved into one or more socket addresses;
267    /// - all the resolved socket addresses are already bound.
268    pub fn bind<F, U, N>(mut self, name: N, addrs: U, factory: F) -> io::Result<Self>
269    where
270        F: ServerServiceFactory<TcpStream>,
271        U: ToSocketAddrs,
272        N: AsRef<str>,
273    {
274        let sockets = bind_addr(addrs, self.backlog, &self.mptcp)?;
275
276        tracing::trace!("binding server to: {sockets:?}");
277
278        for lst in sockets {
279            let token = self.next_token();
280
281            self.factories.push(StreamNewService::create(
282                name.as_ref().to_string(),
283                token,
284                factory.clone(),
285                lst.local_addr()?,
286            ));
287
288            self.sockets
289                .push((token, name.as_ref().to_string(), MioListener::Tcp(lst)));
290        }
291
292        Ok(self)
293    }
294
295    /// Adds service to the server using a socket listener already bound.
296    ///
297    /// # Worker Count
298    ///
299    /// The `factory` will be instantiated multiple times in most scenarios. The number of
300    /// instantiations is: number of [`workers`](Self::workers()).
301    pub fn listen<F, N: AsRef<str>>(
302        mut self,
303        name: N,
304        lst: StdTcpListener,
305        factory: F,
306    ) -> io::Result<Self>
307    where
308        F: ServerServiceFactory<TcpStream>,
309    {
310        lst.set_nonblocking(true)?;
311        let addr = lst.local_addr()?;
312
313        let token = self.next_token();
314        self.factories.push(StreamNewService::create(
315            name.as_ref().to_string(),
316            token,
317            factory,
318            addr,
319        ));
320
321        self.sockets
322            .push((token, name.as_ref().to_string(), MioListener::from(lst)));
323
324        Ok(self)
325    }
326
327    /// Starts processing incoming connections and return server controller.
328    ///
329    /// # Panics
330    ///
331    /// Panics if no sockets are bound.
332    pub fn run(self) -> Server {
333        if self.sockets.is_empty() {
334            panic!("Server should have at least one bound socket");
335        } else {
336            tracing::info!("starting {} workers", self.threads);
337            Server::new(self)
338        }
339    }
340
341    fn next_token(&mut self) -> usize {
342        let token = self.token;
343        self.token += 1;
344        token
345    }
346}
347
348#[cfg(unix)]
349impl ServerBuilder {
350    /// Adds new service to the server using a UDS (unix domain socket) address.
351    ///
352    /// # Worker Count
353    ///
354    /// The `factory` will be instantiated multiple times in most scenarios. The number of
355    /// instantiations is: number of [`workers`](Self::workers()).
356    pub fn bind_uds<F, U, N>(self, name: N, addr: U, factory: F) -> io::Result<Self>
357    where
358        F: ServerServiceFactory<actix_rt::net::UnixStream>,
359        N: AsRef<str>,
360        U: AsRef<std::path::Path>,
361    {
362        // The path must not exist when we try to bind.
363        // Try to remove it to avoid bind error.
364        if let Err(err) = std::fs::remove_file(addr.as_ref()) {
365            // NotFound is expected and not an issue. Anything else is.
366            if err.kind() != std::io::ErrorKind::NotFound {
367                return Err(err);
368            }
369        }
370
371        let lst = crate::socket::StdUnixListener::bind(addr)?;
372        self.listen_uds(name, lst, factory)
373    }
374
375    /// Adds new service to the server using a UDS (unix domain socket) listener already bound.
376    ///
377    /// Useful when running as a systemd service and a socket FD is acquired externally.
378    ///
379    /// # Worker Count
380    ///
381    /// The `factory` will be instantiated multiple times in most scenarios. The number of
382    /// instantiations is: number of [`workers`](Self::workers()).
383    pub fn listen_uds<F, N: AsRef<str>>(
384        mut self,
385        name: N,
386        lst: crate::socket::StdUnixListener,
387        factory: F,
388    ) -> io::Result<Self>
389    where
390        F: ServerServiceFactory<actix_rt::net::UnixStream>,
391    {
392        use std::net::{IpAddr, Ipv4Addr};
393
394        lst.set_nonblocking(true)?;
395
396        let token = self.next_token();
397        let addr = crate::socket::StdSocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8080);
398
399        self.factories.push(StreamNewService::create(
400            name.as_ref().to_string(),
401            token,
402            factory,
403            addr,
404        ));
405
406        self.sockets
407            .push((token, name.as_ref().to_string(), MioListener::from(lst)));
408
409        Ok(self)
410    }
411}
412
413pub(super) fn bind_addr<S: ToSocketAddrs>(
414    addr: S,
415    backlog: u32,
416    mptcp: &MpTcp,
417) -> io::Result<Vec<MioTcpListener>> {
418    let mut opt_err = None;
419    let mut success = false;
420    let mut sockets = Vec::new();
421
422    for addr in addr.to_socket_addrs()? {
423        match create_mio_tcp_listener(addr, backlog, mptcp) {
424            Ok(lst) => {
425                success = true;
426                sockets.push(lst);
427            }
428            Err(err) => opt_err = Some(err),
429        }
430    }
431
432    if success {
433        Ok(sockets)
434    } else if let Some(err) = opt_err.take() {
435        Err(err)
436    } else {
437        Err(io::Error::other("Can not bind to address."))
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::ServerBuilder;
444
445    #[test]
446    #[should_panic(expected = "workers must not exceed 512")]
447    fn rejects_worker_count_above_limit() {
448        ServerBuilder::new().workers(513);
449    }
450
451    #[test]
452    #[should_panic(expected = "workers must be greater than 0")]
453    fn rejects_zero_workers() {
454        ServerBuilder::new().workers(0);
455    }
456
457    #[test]
458    fn accepts_worker_count_boundaries() {
459        ServerBuilder::new().workers(1);
460        ServerBuilder::new().workers(512);
461    }
462
463    #[test]
464    fn default_worker_count_is_capped() {
465        for detected in [513, 768, 1920, usize::MAX] {
466            assert_eq!(ServerBuilder::with_default_workers(detected).threads, 512);
467        }
468    }
469
470    #[test]
471    fn oversized_default_worker_count_can_be_overridden() {
472        assert_eq!(
473            ServerBuilder::with_default_workers(768).workers(1).threads,
474            1
475        );
476    }
477
478    #[test]
479    fn supported_default_worker_count_is_preserved() {
480        for detected in [1, 2, 64, 512] {
481            assert_eq!(
482                ServerBuilder::with_default_workers(detected).threads,
483                detected
484            );
485        }
486    }
487}