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.
Why It Matters
Section titled “Why It Matters”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
Example
Section titled “Example”# ❌ 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 asynccontextmanagerfrom fastapi import FastAPIimport logging
logger = logging.getLogger(__name__)
@asynccontextmanagerasync 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)What Unfault Detects
Section titled “What Unfault Detects”- FastAPI/Flask/Django apps without signal handlers
- Web applications missing lifespan context managers
- Missing
atexitregistration - Applications without
SIGTERMhandling
Auto-Fix
Section titled “Auto-Fix”For FastAPI applications, Unfault adds a lifespan context manager:
from contextlib import asynccontextmanager
@asynccontextmanagerasync 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 signalimport 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)Kubernetes Deployment
Section titled “Kubernetes Deployment”# Ensure adequate termination grace periodspec: terminationGracePeriodSeconds: 30 containers: - name: app lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 5"] # Allow LB to drain