From 0e14d2761a0ca4296548104099735fb9c0d53ef8 Mon Sep 17 00:00:00 2001 From: giles Date: Wed, 25 Feb 2026 01:35:11 +0000 Subject: [PATCH] Fix L2 deployment: healthcheck, DB deadlock, CI image resolution - Add /health endpoint (returns 200, skips auth middleware) - Healthcheck now hits /health instead of / (which 302s to OAuth) - Advisory lock in db.init_pool() prevents deadlock when 4 uvicorn workers race to run schema DDL - CI: --resolve-image always on docker stack deploy to force re-pull Co-Authored-By: Claude Opus 4.6 --- .gitea/workflows/ci.yml | 6 +++--- l2/app/__init__.py | 5 +++++ l2/db.py | 11 +++++++++-- l2/docker-compose.yml | 2 +- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index c2bf29e..d0fa8a7 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -96,13 +96,13 @@ jobs: echo 'Skipping L2 (no changes)' fi - # Deploy stacks + # Deploy stacks (--resolve-image always forces re-pull of :latest) if [ \"\$BUILD_L1\" = true ]; then - cd l1 && source .env && docker stack deploy -c docker-compose.yml celery && cd .. + cd l1 && source .env && docker stack deploy --resolve-image always -c docker-compose.yml celery && cd .. echo 'L1 stack deployed' fi if [ \"\$BUILD_L2\" = true ]; then - cd l2 && source .env && docker stack deploy -c docker-compose.yml activitypub && cd .. + cd l2 && source .env && docker stack deploy --resolve-image always -c docker-compose.yml activitypub && cd .. echo 'L2 stack deployed' fi diff --git a/l2/app/__init__.py b/l2/app/__init__.py index e4938e2..fbc713a 100644 --- a/l2/app/__init__.py +++ b/l2/app/__init__.py @@ -160,6 +160,11 @@ def create_app() -> FastAPI: template_dir = Path(__file__).parent / "templates" app.state.templates = create_jinja_env(template_dir) + # Health check (skips auth middleware via _SKIP_PREFIXES) + @app.get("/health") + async def health(): + return JSONResponse({"status": "ok"}) + # Custom 404 handler @app.exception_handler(404) async def not_found_handler(request: Request, exc): diff --git a/l2/db.py b/l2/db.py index 205271d..465826c 100644 --- a/l2/db.py +++ b/l2/db.py @@ -187,9 +187,16 @@ async def init_pool(): max_size=10, command_timeout=60 ) - # Create tables if they don't exist + # Create tables if they don't exist (advisory lock prevents deadlock + # when multiple uvicorn workers start simultaneously) async with _pool.acquire() as conn: - await conn.execute(SCHEMA) + acquired = await conn.fetchval("SELECT pg_try_advisory_lock(42)") + if acquired: + try: + await conn.execute(SCHEMA) + finally: + await conn.execute("SELECT pg_advisory_unlock(42)") + # If another worker holds the lock, schema is being created — skip async def close_pool(): diff --git a/l2/docker-compose.yml b/l2/docker-compose.yml index 9c91ea4..afb5644 100644 --- a/l2/docker-compose.yml +++ b/l2/docker-compose.yml @@ -60,7 +60,7 @@ services: - OAUTH_LOGOUT_URL=https://account.rose-ash.com/auth/sso-logout/ # DATABASE_URL, ARTDAG_DOMAIN, ARTDAG_USER, JWT_SECRET, SECRET_KEY from .env file healthcheck: - test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8200/')"] + test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8200/health')"] interval: 10s timeout: 5s retries: 3