diff --git a/app/main.py b/app/main.py index 28129f3..2a4fe4d 100644 --- a/app/main.py +++ b/app/main.py @@ -128,6 +128,15 @@ async def startup(): from .queue import start_worker, re_enqueue_pending await re_enqueue_pending() start_worker() + + +@app.on_event("shutdown") +async def shutdown(): + """Graceful Shutdown: warte auf laufende Queue-Jobs bevor der Container stirbt.""" + from .queue import graceful_shutdown + await graceful_shutdown(timeout=300) + + # JSON import disabled - all assessments now live in SQLite DB only # Legacy import would overwrite new v5 assessments with old format # count = await import_json_assessments(settings.data_dir / "assessments") diff --git a/app/queue.py b/app/queue.py index 4f2a195..482e618 100644 --- a/app/queue.py +++ b/app/queue.py @@ -158,6 +158,27 @@ def start_worker() -> list[asyncio.Task]: return _worker_tasks +async def graceful_shutdown(timeout: int = 300): + """Wait for running jobs to finish before shutdown. + + Called from FastAPI shutdown event. Waits up to `timeout` seconds + for the queue to drain and workers to finish their current job. + """ + pending = _queue.qsize() + processing = sum(1 for j in _jobs.values() if j.get("status") == "processing") + if pending == 0 and processing == 0: + logger.info("Queue empty, shutdown immediately") + return + + logger.warning("Graceful shutdown: waiting for %d pending + %d processing jobs (max %ds)", + pending, processing, timeout) + try: + await asyncio.wait_for(_queue.join(), timeout=timeout) + logger.info("Queue drained, shutdown clean") + except asyncio.TimeoutError: + logger.error("Graceful shutdown timeout after %ds, %d jobs still pending", timeout, _queue.qsize()) + + async def re_enqueue_pending(): """Mark stale queued jobs from previous run.""" import aiosqlite diff --git a/docker-compose.yml b/docker-compose.yml index 5d27b02..257f01b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,7 @@ services: build: . container_name: gwoe-antragspruefer restart: unless-stopped + stop_grace_period: 5m # Queue-Jobs zu Ende laufen lassen vor Kill environment: - DASHSCOPE_API_KEY=${DASHSCOPE_API_KEY} - KEYCLOAK_URL=https://sso.toppyr.de