singleserver
Manage singleton server processes across multiple workers using atomic socket binding.
Problem
When running web applications with multiple workers (e.g., gunicorn), you often need auxiliary services (API servers, background processors, caches) that should only run as a single instance shared by all workers.
Current solutions like systemd unit files or separate containers require additional infrastructure and make local development different from production.
Solution
singleserver uses the OS-level guarantee that only one process can bind a socket at a time. When multiple workers call connect():
- First worker acquires the lock and starts the server
- Other workers detect the lock is held and connect as clients
- If the owner dies, another worker can take over
Worker 1: connect() → bind(:18765) → SUCCESS → starts server, returns client
Worker 2: connect() → bind(:18765) → EADDRINUSE → waits for ready, returns client
Worker 3: connect() → bind(:18765) → EADDRINUSE → waits for ready, returns client
Installation
pip install singleserver
Quick Start
from singleserver import SingleServer
# Define the server
api_server = SingleServer(
name="api-server",
command=["python", "-m", "my_api", "--port", "{port}"],
port=8765,
)
# Connect (starts if needed, connects if already running)
with api_server.connect() as client:
response = client.get("/data.json")
print(response.json())
Features
- Atomic coordination: Uses socket binding for distributed locking
- Health monitoring: Configurable health checks with automatic restarts
- Output handling: Redirect stdout/stderr to files
- Graceful shutdown: SIGTERM with timeout, then SIGKILL
- No external dependencies: Pure Python, no Redis/etcd/etc needed
- Same code for dev and prod: No systemd unit files required
Configuration
SingleServer(
name="my-service", # Identifier for logging
command=["python", "server.py", ...], # Command to run ({port} is replaced)
# Server address
port=8765, # TCP port
# OR
socket="/tmp/my-service.sock", # Unix socket (faster, more secure)
# Health checks
health_check_url="/", # URL to poll for readiness
health_check_interval=5.0, # Seconds between checks
startup_timeout=30.0, # Max seconds to wait for startup
# Restart behavior
restart_on_failure=True, # Auto-restart if process dies
max_restarts=3, # Give up after N restarts
restart_delay=1.0, # Seconds between restart attempts
# Process options
env={"DATABASE_URL": "..."}, # Environment variables
cwd="/app", # Working directory
stdout="/var/log/my-service.log", # Log file
stderr="stdout", # Redirect stderr to stdout
# Shutdown
shutdown_timeout=10.0, # Seconds for graceful stop
)
Use Cases
Internal API server
from singleserver import SingleServer
api = SingleServer(
name="internal-api",
command=["python", "-m", "internal_api", "-p", "{port}"],
port=8765,
)
def my_view(request):
with api.connect() as client:
data = client.get("/query").json()
return JsonResponse(data)
Background service
service = SingleServer(
name="background-service",
command=["python", "service.py", "--port", "{port}"],
port=8888,
health_check_url="/health",
restart_on_failure=True,
)
How It Works
- Lock acquisition: Uses a separate socket (port + 10000 by default) for coordination
- Process management: Owner spawns subprocess in new process group
- Health monitoring: Background thread polls health endpoint
- Restart logic: Configurable restart attempts with delay
- Cleanup: atexit handler and signal handlers for clean shutdown
Development
# Install dev dependencies
uv sync --all-extras
# Run tests
uv run pytest
# Type checking
uv run mypy singleserver
# Linting
uv run ruff check singleserver tests
License
MIT
Metadata
Release files for singleserver 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| singleserver-0.4.0.tar.gz | 84.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| singleserver-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.1 kB
Release files / singleserver-0.4.0.tar.gz
| Download URL | singleserver-0.4.0.tar.gz |
|---|---|
| Size | 84.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cab015d4c85acd77187878e4e0843239c1305ca567e446b6e5fe8f83dcadfbea
|
|
BLAKE2b-256 checksum How to use checksums |
d90760acbe286c6bea14cee4e5342c8a9fea25a91bba2d818b2fece349fc49a5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|
Release files / singleserver-0.4.0-py3-none-any.whl
| Download URL | singleserver-0.4.0-py3-none-any.whl |
|---|---|
| Size | 17.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
867762d5b26c393137a9dbcf033e85032c6a91375793bca088511147ba2be895
|
|
BLAKE2b-256 checksum How to use checksums |
8ca03a1af4e740cdbcb6b3bd0da0e21d72642b68723974fe744c5ea7dbbcbe87
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.2
|