From d273146138898308428f4ccfa4c7dbfe1001925b Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Thu, 28 May 2026 10:04:15 +0200 Subject: [PATCH] feat(server): add OXICLOUD_REUSE_PORT env parameter (false by default) initially server was accepting multiple instances on same port (Linux and MacOS only) this can create issues during development (if a dev forget another running instance...) Add OXICLOUD_REUSE_PORT variable to activate it, so only admins knowing this feature can activate itt (permits multiple instance + let the OS schedduler to decide which process will handle a request) if not enabled, other instance will receive exit with a: `Error: Os { code: 48, kind: AddrInUse, message: "Address already in use" }` --- docs/config/env.md | 3 ++- example.env | 8 ++++++++ src/main.rs | 29 +++++++++++++++++++++++++---- 3 files changed, 35 insertions(+), 5 deletions(-) diff --git a/docs/config/env.md b/docs/config/env.md index b07f5571..9bb133fa 100644 --- a/docs/config/env.md +++ b/docs/config/env.md @@ -9,9 +9,10 @@ Most runtime variables use the `OXICLOUD_` prefix. A few build-time or allocator | `OXICLOUD_STORAGE_PATH` | `./storage` | Root storage directory | | `OXICLOUD_STATIC_PATH` | `./static` | Static files directory | | `OXICLOUD_SERVER_PORT` | `8086` | Server port | -| `OXICLOUD_SERVER_HOST` | `127.0.0.1` | Server bind addressi (IPv4 or IPv6 allowed) | +| `OXICLOUD_SERVER_HOST` | `127.0.0.1` | Server bind address (IPv4 or IPv6 allowed) | | `OXICLOUD_BASE_URL` | (auto) | Public base URL for share links; defaults to `http://{host}:{port}` | | `OXICLOUD_MAX_UPLOAD_SIZE` | `10737418240` | Maximum upload size in bytes (10 GB on 64-bit, 1 GB on 32-bit) | +| `OXICLOUD_REUSE_PORT` | `false` | Enable `SO_REUSEPORT` so multiple processes can share the same port. **Disabled by default** — a second accidental instance will fail with "address already in use". Enable only for deliberate multi-worker setups (process supervisor, rolling restart). Not supported on Windows. | ## Database diff --git a/example.env b/example.env index 7a83a0e8..3c175407 100644 --- a/example.env +++ b/example.env @@ -34,6 +34,14 @@ OXICLOUD_SERVER_HOST=127.0.0.1 # Maximum upload size in bytes (default: 10 GB on 64-bit) #OXICLOUD_MAX_UPLOAD_SIZE=10737418240 +# Allow multiple processes to bind to the same port (SO_REUSEPORT). +# DISABLED by default — leaving this off means a second accidental instance +# will fail immediately with "address already in use", which is the safe behaviour. +# Enable ONLY when you deliberately run several worker processes in parallel +# (e.g. behind a process supervisor or during a zero-downtime rolling restart). +# Not supported on Windows. +#OXICLOUD_REUSE_PORT=false + # ----------------------------------------------------------------------------- # DATABASE CONFIGURATION # ----------------------------------------------------------------------------- diff --git a/src/main.rs b/src/main.rs index 8ba921f5..c397174b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,7 @@ fn parse_addr(host: &str, port: u16) -> Result { .map_err(|e| format!("Invalid address '{}': {}", addr_str, e)) } -fn make_socket(addr: &SocketAddr) -> std::io::Result { +fn make_socket(addr: &SocketAddr, reuse_port: bool) -> std::io::Result { let domain = if addr.is_ipv6() { Domain::IPV6 } else { @@ -85,9 +85,14 @@ fn make_socket(addr: &SocketAddr) -> std::io::Result { }; let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))?; socket.set_reuse_address(true)?; - // Allow multiple workers on the same port (future-ready) + // SO_REUSEPORT: opt-in only — must be explicitly enabled via + // OXICLOUD_REUSE_PORT=true. Disabled by default so that accidentally + // starting a second instance fails fast with "address already in use" + // rather than silently sharing the port. #[cfg(not(windows))] - socket.set_reuse_port(true)?; + if reuse_port { + socket.set_reuse_port(true)?; + } // Disable Nagle's algorithm — send small responses (JSON, PROPFIND) // immediately instead of waiting up to 40ms for coalescing. socket.set_tcp_nodelay(true)?; @@ -566,9 +571,25 @@ async fn main() -> Result<(), Box> { // Start server — tuned socket for low-latency responses // TODO: suport multiple addresses ? let addr = parse_addr(&config.server_host, config.server_port)?; + + // SO_REUSEPORT: disabled by default — a second instance on the same port + // fails loudly instead of silently sharing the socket. Set + // OXICLOUD_REUSE_PORT=true only when you deliberately run multiple + // workers (e.g. behind a process supervisor or during a rolling restart). + let reuse_port = std::env::var("OXICLOUD_REUSE_PORT") + .map(|v| v.eq_ignore_ascii_case("true") || v == "1") + .unwrap_or(false); + + if reuse_port { + tracing::warn!( + "OXICLOUD_REUSE_PORT is enabled — multiple processes may bind to port {}", + config.server_port + ); + } + tracing::info!("Starting OxiCloud server on http://{}", addr); - let socket = make_socket(&addr)?; + let socket = make_socket(&addr, reuse_port)?; let listener = tokio::net::TcpListener::from_std(socket.into())?;