Skip to content

python.resilience.missing_graceful_shutdown

Stability High

Detects applications that don’t handle SIGTERM signal for graceful shutdown. Without graceful shutdown, requests are dropped during deployments.

During deployments, orchestrators like Kubernetes send SIGTERM to allow apps to finish in-flight requests:

  • Dropped requests — Users see errors during deployments
  • Data corruption — Half-completed transactions may corrupt data
  • Resource leaks — Connections aren’t properly closed
  • Extended downtime — Hard kills after SIGTERM timeout take longer
# ❌ Before (no graceful shutdown)
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def root():
return {"message": "Hello"}
# ✅ After (with graceful shutdown using lifespan)
from contextlib import asynccontextmanager
from fastapi import FastAPI
import logging
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup: Initialize resources
logger.info("Application starting up")
yield
# Shutdown: Clean up resources
logger.info("Shutting down gracefully")
# Close database connections, flush caches, etc.
app = FastAPI(lifespan=lifespan)
  • FastAPI/Flask/Django apps without signal handlers
  • Web applications missing lifespan context managers
  • Missing atexit registration
  • Applications without SIGTERM handling

For FastAPI applications, Unfault adds a lifespan context manager:

from contextlib import asynccontextmanager
@asynccontextmanager
async def lifespan(app):
# Startup
logger.info("Application starting up")
yield
# Shutdown
logger.info("Application shutting down gracefully")

For generic Python applications, it adds signal handlers:

import signal
import sys
def graceful_shutdown(signum, frame):
"""Handle shutdown signals gracefully."""
logger.info(f"Received signal {signum}, shutting down gracefully...")
sys.exit(0)
signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)
# Ensure adequate termination grace period
spec:
terminationGracePeriodSeconds: 30
containers:
- name: app
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 5"] # Allow LB to drain