listenfd

repository·master·Indexed 42 days ago

https://github.com/mitsuhiko/listenfd

A Rust crate for working with externally managed file descriptors, facilitating socket activation patterns used by systemd and systemfd. It allows applications to accept TCP, UDP, and UNIX sockets passed from a supervisor process, preventing connection resets during hot-reloading in development workflows.

Tokens
3.6K
Snippets
20
Records
23
Agent score
54%

What's inside listenfd

  1. What is listenfd and when to use it

    master

    listenfd is a Rust crate designed to work with externally managed and passed file descriptors. This is useful for systems that support socket activation, allowing a supervisor process to open a socket and pass it to your application.

    It currently supports:

    • systemd (including systemd-socket-activate) on Unix for production deployments.
    • systemfd on Unix and Windows, which is highly recommended for development workflows to prevent connection resets during hot-reloading.
  2. Set up a development workflow with systemfd and cargo-watch

    master

    For a seamless development experience where the socket remains open even when your server restarts (preventing connection resets), use systemfd in combination with cargo-watch or watchexec.

    Run the following commands to start your server with a managed socket:

    Using cargo-watch:

    systemfd --no-pid -s http::3000 -- cargo watch -x run

    Using watchexec:

    systemfd --no-pid -s http::3000 -- watchexec -r -- cargo run
  3. Run the Axum example with systemfd and watchexec

    master

    To run the Axum demonstration using systemfd combined with watchexec (for hot reloading), use the following command. This setup allows the application to listen on a file descriptor provided by systemfd while automatically restarting on code changes.

    systemfd --no-pid -s http::3030 -- watchexec -r -- cargo run
  4. Run the warp example with systemfd and cargo-watch

    master

    To run the warp-demo example while automatically restarting the server on file changes, use systemfd to manage the file descriptor and cargo-watch to monitor the source code. This setup allows the application to receive a pre-opened listening socket from the system.

    systemfd --no-pid -s http::3030 -- cargo watch -x run
  5. Run the warp example with systemfd and watchexec

    master

    To run the warp-demo example using watchexec for hot-reloading, use the following command. This ensures the process is restarted whenever changes are detected, with systemfd providing the socket on port 3030.

    systemfd --no-pid -s http::3030 -- watchexec -r -- cargo run
  6. Run the Axum example with systemfd and cargo-watch

    master

    To run the Axum demonstration using systemfd (to pass a file descriptor) combined with cargo-watch (for hot reloading), use the following command. This setup allows the application to listen on a file descriptor provided by systemfd while automatically restarting on code changes.

    systemfd --no-pid -s http::3030 -- cargo watch -x run
  7. How ListenFd handles socket activation protocols

    master

    The crate supports two primary protocols for receiving file descriptors:

    1. Unix: A slightly modified version of the systemd protocol. If the LISTEN_PID environment variable is unset or empty, the PID check is skipped. This is critical when using proxies like cargo-watch between the socket manager and your binary.
    2. Windows: A custom protocol implemented for Windows environments.

    For local development, you can use the systemfd utility to implement these protocols.

  8. Retrieve sockets using ListenFd

    master

    The ListenFd struct allows you to "take" specific file descriptors from its internal collection and convert them into standard Rust networking types.

    Key behaviors:

    • One-time use: Once a descriptor is taken via a take_* method, it is removed from the ListenFd manager. Subsequent calls to the same index will return Ok(None).
    • Validation: If the descriptor at the specified index does not match the requested type (e.g., trying to take a TcpListener from a UDP socket), an io::Error is returned and the descriptor remains in the manager.
    • Indexing: Access is provided via a zero-based index (usize).
  9. Use ListenFd to handle externally passed file descriptors

    master

    The ListenFd struct allows your application to accept file descriptors (like TCP listeners) passed from an external manager (e.g., systemd or systemfd). This is useful for socket activation, ensuring that sockets remain open even when your application restarts (for example, during development with cargo watch).

    To use it, initialize ListenFd using ListenFd::from_env(), then attempt to take a specific listener using take_tcp_listener(index). If a listener is provided by the environment, you use it; otherwise, you fall back to standard local binding.

    use listenfd::ListenFd;
    
    // Initialize from environment variables
    let mut listenfd = ListenFd::from_env();
    let mut server = make_a_server();
    
    // Attempt to take a TCP listener from the first passed file descriptor (index 0)
    server = if let Some(listener) = listenfd.take_tcp_listener(0)? {
        server.listen(listener)
    } else {
        // Fallback to standard local binding if no FD is provided
        server.bind("127.0.0.1:3000")?
    };
  10. Use ListenFd to handle socket activation

    master

    To use listenfd, initialize a ListenFd instance using ListenFd::from_env(). You can then attempt to take specific listeners (like TCP) from the environment. If a listener is provided by the supervisor (e.g., systemd or systemfd), use it; otherwise, fall back to standard local binding.

    Use take_tcp_listener(index) to retrieve a TCP listener at a specific index.

    use listenfd::ListenFd;
    
    let mut listenfd = ListenFd::from_env();
    let mut server = make_a_server();
    
    // if we are given a tcp listener on listen fd 0, we use that one
    server = if let Some(listener) = listenfd.take_tcp_listener(0)? {
        server.listen(listener)
    // otherwise fall back to local listening
    } else {
        server.bind("127.0.0.1:3000")?
    };
  11. Develop with systemfd and cargo-watch

    master

    To prevent connection resets during development when using cargo watch, use systemfd to manage the socket. Use the --no-pid flag with systemfd to ensure the LISTEN_PID environment variable is not set, allowing listenfd to bypass the PID check.

    Example workflow:

    $ cargo install systemfd cargo-watch
    $ systemfd --no-pid -s 3000 -- cargo watch -x run
  12. Convert file descriptors to TCP, UDP, or Unix listeners

    master

    The src/unix.rs module provides helper functions to wrap raw file descriptors (FdType) into standard Rust networking types. All functions perform validation to ensure the descriptor is a valid socket of the expected type and family, and they automatically set the FD_CLOEXEC flag on the descriptor.

    Available Conversion Functions

    FunctionReturnsExpected Socket TypeExpected Family
    make_tcp_listener(fd)io::Result<TcpListener>SOCK_STREAMAF_INET
    make_unix_listener(fd)io::Result<UnixListener>SOCK_STREAMAF_UNIX
    make_udp_socket(fd)io::Result<UdpSocket>SOCK_DGRAMAF_INET
    make_unix_datagram(fd)io::Result<UnixDatagram>SOCK_DGRAMAF_UNIX
    make_custom<T>(fd, fam, type, hint)io::Result<T>User-specifiedUser-specified

    Custom Conversions

    If you need a specific type not covered by the helpers, use make_custom. You must provide the libc constants for the socket family and type, along with a hint string used for error reporting.

    use listenfd::make_tcp_listener;
    
    // Assuming 'fd' was obtained via get_fds()
    let listener = make_tcp_listener(fd).expect("Failed to create TCP listener");