-
-
Notifications
You must be signed in to change notification settings - Fork 147
Description
Hi! First of all, thank you for spending your valuable personal time building Granian — it’s an excellent high-performance piece of software.
I’d like to propose adding support for systemd Socket Activation to Granian.
Background & Motivation
In single-host deployments, a common approach to achieve “zero downtime” restarts is to rely on SIGHUP-based reloads. However, a pure reload mechanism has limitations in the following cases, because it typically reuses the same master process:
- Environment variable updates: systemd
Environment=values are usually injected only when the process starts. Whensystemctl reloadsends a signal to the main process, the PID stays the same, so the process won’t pick up changes made to environment variables in the unit file. - Binary / dependency updates: When upgrading Granian itself, critical dependencies (e.g., uvloop), or even a Python patch version (e.g., 3.13.0 -> 3.13.1), it’s hard to be confident the updated libraries are actually loaded unless the process is fully restarted.
Proposed Solution
systemd Socket Activation is a best-practice solution for the issues above, especially for cost-effective single-host deployments. It doesn’t require an external load balancer; it only depends on systemd, yet can deliver production-grade high availability:
- Truly smooth restarts:
systemctl restartstarts a completely new process. During the transition, systemd holds the listening socket and queues incoming connections (so instead of “Connection refused”, clients typically experience a brief delay) until the new process takes over. - Strong isolation: Each restart launches a fresh process, which cleanly solves both environment refresh and dependency upgrade concerns.
Notably, many popular Python WSGI/ASGI servers (e.g., Gunicorn, uWSGI, Hypercorn, Uvicorn) already support this deployment model, and it has been widely proven as a standard and reliable production practice.
Feasibility Notes
I took a quick look at Granian’s source code, and it seems the underlying Rust implementation may already have the necessary building blocks,
Unfortunately, I'm not yet capable of finishing the changes required for the PR, so I haven't submitted it."
In src/net.rs, the SocketHolder type already exposes a constructor that can initialize from an existing file descriptor (FD):
#[cfg(any(target_os = "linux", target_os = "freebsd"))]
#[pymethods]
impl SocketHolder {
#[new]
pub fn new(fd: i32, uds: bool, backlog: i32) -> Self {
let socket = unsafe { Socket::from_raw_fd(fd) };
// ...
}
}
This suggests the Rust backend can already accept a pre-opened socket. If the Python-side initialization logic (e.g., granian/server/common.py) could detect the LISTEN_FDS environment variable and pass the corresponding FD(s) into SocketHolder, it might be possible to implement socket activation with relatively small changes (based on my limited understanding).
If Granian could start by inheriting systemd-provided file descriptors, it would significantly improve production usability.
(If anything above is incorrect, I’m sorry — some of this text was produced with the help of a translation tool and may be a bit rough.)
Thanks again for your great work!