Skip to main content

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

  1. The JSON config is loaded and validated.
  2. Dependencies are resolved via topological sort (Kahn's algorithm). A CycleDetectedError is raised immediately if a cycle is found.
  3. 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.
  4. Each service is launched via subprocess.Popen.
  5. Without readiness_check: the process is observed for a short window. If it exits during that window, startup fails.
  6. With readiness_check: the check command is polled until it exits with code 0, or startup_timeout is exceeded.
  7. If any service fails to start, all already-running services are shut down in reverse order before StartupError is raised.

Shutdown

  1. Services stop in reverse dependency order (reverse alphabetical within each level).
  2. Each service receives a graceful termination signal.
  3. If a service does not exit within shutdown_timeout, it is force-killed.
  4. stop() is best-effort — it always returns a LifecycleResult and 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

svcdag-0.1.0.tar.gz (15.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

svcdag-0.1.0-py3-none-any.whl (10.7 kB view details)

Uploaded Python 3

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

Hashes for svcdag-0.1.0.tar.gz
Algorithm Hash digest
SHA256 4f12ab456f68e9ca8a10a8b37faf93da3a14d2f97777f87ada7d7a698874ab5a
MD5 8a322adf1d5f19c30ca31a0a98b68490
BLAKE2b-256 f086422e2536526620193e36ee58eefa53b230ae1478842fa3e49112260f5c3c

See more details on using hashes here.

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

Hashes for svcdag-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b8ee7b71d3af31c4a6573251d46d3a2588a4e54bf552dece8ee7614b7badded5
MD5 e987b47df342b124caf7e20d4f1a4de4
BLAKE2b-256 473885d56a9282af755d150d05794186c6c1ebd0ce5323ab5f1f26e514b723b9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page