diff --git a/api/main.py b/api/main.py index 6028d80..a50d9d6 100644 --- a/api/main.py +++ b/api/main.py @@ -293,12 +293,17 @@ class DashboardHandler(BaseHTTPRequestHandler): def _send_json(self, payload, status: int = 200) -> None: body = _dump(payload) - self.send_response(status) - self.send_header("Content-Type", "application/json") - self.send_header("Content-Length", str(len(body))) - self.send_header("Access-Control-Allow-Origin", "*") - self.end_headers() - self.wfile.write(body) + try: + self.send_response(status) + self.send_header("Content-Type", "application/json") + self.send_header("Content-Length", str(len(body))) + self.send_header("Access-Control-Allow-Origin", "*") + self.end_headers() + self.wfile.write(body) + except (BrokenPipeError, ConnectionResetError): + # the client hung up while we were rendering (slow query + browser + # timeout) — nothing to send it; don't double-traceback the log + pass def _send_html(self, path: Path) -> None: if not path.exists(): diff --git a/deploy/docker/truenas-compose.yml b/deploy/docker/truenas-compose.yml index 47ebe47..c3223f6 100644 --- a/deploy/docker/truenas-compose.yml +++ b/deploy/docker/truenas-compose.yml @@ -78,7 +78,10 @@ services: <<: *app command: ["python", "-u", "run_live_paper.py", "--no-dashboard"] healthcheck: - test: ["CMD", "python", "-c", "import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"] + # PG_SKIP_SCHEMA=1: a healthcheck must NOT re-apply schema DDL — the 60s + # CREATE-IF-NOT-EXISTS lock burst cancelled minute_bars autovacuum forever + # (2026-07-09 post-migration: stats rotted, dashboard queries crawled) + test: ["CMD", "python", "-c", "import os; os.environ['PG_SKIP_SCHEMA']='1'; import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"] interval: 60s timeout: 20s retries: 5 diff --git a/docker-compose.yml b/docker-compose.yml index b14a02d..12b59cd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,7 +62,10 @@ services: healthcheck: # heartbeat: the loop must have written SOME event in the last 5 minutes # (module ticks flow around the clock, not just market hours) - test: ["CMD", "python", "-c", "import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"] + # PG_SKIP_SCHEMA=1: a healthcheck must NOT re-apply schema DDL — the 60s + # CREATE-IF-NOT-EXISTS lock burst cancelled minute_bars autovacuum forever + # (2026-07-09 post-migration: stats rotted, dashboard queries crawled) + test: ["CMD", "python", "-c", "import os; os.environ['PG_SKIP_SCHEMA']='1'; import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"] interval: 60s timeout: 20s retries: 5