Deterministic startup and shutdown of dependent services via a DAG
Project description
svcdag
Deterministic startup and shutdown of dependent services via a DAG.
svcdag is a Python library for orchestrating external subprocesses in dependency order. Define your services in a JSON config file; svcdag resolves the dependency graph, starts everything in the right order, and shuts it all down cleanly — with or without readiness checks.
Install
pip install svcdag
Quick start
from svcdag import Orchestrator
with Orchestrator.from_file("services.json") as orch:
input("Services running. Press Enter to shut down...")
# all services stopped automatically
Or manage lifecycle manually:
from svcdag import Orchestrator, StartupError
orch = Orchestrator.from_file("services.json")
try:
result = orch.start()
print(f"Started: {result.started}")
except StartupError as e:
print(f"Failed: {e.failed_service}")
print(f"Never reached: {e.never_started}")
finally:
result = orch.stop()
if result.force_killed:
print(f"Force-killed: {result.force_killed}")
Config format
{
"services": {
"database": {
"command": ["./bin/database", "--port", "5432"],
"dependencies": [],
"startup_timeout": 5.0,
"shutdown_timeout": 5.0,
"readiness_check": ["./bin/check_db_ready"]
},
"cache": {
"command": ["./bin/cache"],
"dependencies": []
},
"api_server": {
"command": ["./bin/api"],
"dependencies": ["database", "cache"]
},
"worker": {
"command": ["./bin/worker"],
"dependencies": ["api_server"]
}
}
}
Service fields
| Field | Required | Default | Description |
|---|---|---|---|
command |
Yes | — | Argv list to launch the process |
dependencies |
No | [] |
Names of services that must be running first |
startup_timeout |
No | 5.0 |
Seconds to wait for the process to become ready |
shutdown_timeout |
No | 5.0 |
Seconds to wait for graceful exit before force-killing |
readiness_check |
No | null |
Command to poll (exit 0 = ready) within startup_timeout |
Behavior
Startup
- The JSON config is loaded and validated.
- Dependencies are resolved via topological sort (Kahn's algorithm). A
CycleDetectedErroris raised immediately if a cycle is found. - Services are started level by level — services with no dependencies first, then their dependents, and so on. Within each level, services start in alphabetical order.
- Each service is launched via
subprocess.Popen. - Without
readiness_check: the process is observed for a short window. If it exits during that window, startup fails. - With
readiness_check: the check command is polled until it exits with code 0, orstartup_timeoutis exceeded. - If any service fails to start, all already-running services are shut down in reverse order before
StartupErroris raised.
Shutdown
- Services stop in reverse dependency order (reverse alphabetical within each level).
- Each service receives a graceful termination signal.
- If a service does not exit within
shutdown_timeout, it is force-killed. stop()is best-effort — it always returns aLifecycleResultand never raises.
API reference
Orchestrator
Orchestrator(services: list[ServiceConfig])
Orchestrator.from_file(path: str | Path) -> Orchestrator
| Method / Property | Description |
|---|---|
start() -> LifecycleResult |
Start all services. Raises StartupError on failure. |
stop() -> LifecycleResult |
Stop all services. Always returns, never raises. |
state -> OrchestratorState | None |
Current state: STARTING, RUNNING, STOPPING, or None. |
| Context manager | __enter__ calls start(), __exit__ calls stop(). |
LifecycleResult
@dataclass
class LifecycleResult:
success: bool
started: list[str] # services successfully started (startup)
failed: str | None # service that caused abort (unused by stop())
never_started: list[str] # services skipped due to cascade failure
force_killed: list[str] # services that had to be force-killed (shutdown)
errors: dict[str, str] # service name -> error message
Exceptions
| Exception | When raised |
|---|---|
SchemaError |
Config file is missing required fields or has invalid types |
CycleDetectedError |
A dependency cycle is detected (.cycle attribute lists involved services) |
StartupError |
A service fails to start (.failed_service, .never_started attributes) |
Debug tool
A show_order.py script is included to inspect the startup order of a config without launching any processes:
python show_order.py services.json
Output:
Config: services.json
Services: 4
=== Startup order (by level) ===
Level 0: cache
database
Level 1: api_server (deps: database, cache)
Level 2: worker (deps: api_server)
=== Flat serial order ===
1. cache
2. database
3. api_server
4. worker
=== Shutdown order (reverse) ===
1. worker
2. api_server
3. database
4. cache
What svcdag is not
- Not a process supervisor — services that crash after startup are not restarted
- Not a config manager — environment variables and config files are your responsibility
- Not a service discovery tool
- Does not persist state across runs
Requirements
- Python 3.11+
- No external dependencies
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file svcdag-0.1.0.tar.gz.
File metadata
- Download URL: svcdag-0.1.0.tar.gz
- Upload date:
- Size: 15.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f12ab456f68e9ca8a10a8b37faf93da3a14d2f97777f87ada7d7a698874ab5a
|
|
| MD5 |
8a322adf1d5f19c30ca31a0a98b68490
|
|
| BLAKE2b-256 |
f086422e2536526620193e36ee58eefa53b230ae1478842fa3e49112260f5c3c
|
File details
Details for the file svcdag-0.1.0-py3-none-any.whl.
File metadata
- Download URL: svcdag-0.1.0-py3-none-any.whl
- Upload date:
- Size: 10.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.10.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b8ee7b71d3af31c4a6573251d46d3a2588a4e54bf552dece8ee7614b7badded5
|
|
| MD5 |
e987b47df342b124caf7e20d4f1a4de4
|
|
| BLAKE2b-256 |
473885d56a9282af755d150d05794186c6c1ebd0ce5323ab5f1f26e514b723b9
|