Spaces:
Sleeping
Sleeping
Fix QueuePool exhaustion: close leaked connections + widen pool + fix 2026 retry loop
Browse files- _fire_completion: add finally block to always close fresh_conn, preventing
daemon threads from leaking connections that exhaust the pool
- remote_db: raise pool_size 5→10, max_overflow 10→20, pool_timeout 30→60
to tolerate burst connections during live game windows
- render_statcast_retry_fragment: exclude games in live_pitch_mix_2026 from
pending query so 2026 games no longer perpetually re-trigger _fire_completion
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- app.py +11 -0
- database/remote_db.py +3 -0
app.py
CHANGED
|
@@ -403,12 +403,19 @@ def _fire_completion(pk_str: str, game_date: str, scores_df: pd.DataFrame) -> No
|
|
| 403 |
scores_snapshot = scores_df.copy()
|
| 404 |
|
| 405 |
def _run() -> None:
|
|
|
|
| 406 |
try:
|
| 407 |
from database.db import get_connection as _get_conn
|
| 408 |
fresh_conn = _get_conn()
|
| 409 |
on_game_complete(fresh_conn, int(pk_str), game_date, scores_snapshot)
|
| 410 |
except Exception as exc:
|
| 411 |
logger.warning("[_fire_completion] thread error game_pk=%s: %s", pk_str, exc)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 412 |
|
| 413 |
t = threading.Thread(target=_run, daemon=True)
|
| 414 |
t.start()
|
|
@@ -475,6 +482,10 @@ def render_statcast_retry_fragment() -> None:
|
|
| 475 |
SELECT 1 FROM statcast_event_core s
|
| 476 |
WHERE s.game_pk = g.game_pk::BIGINT
|
| 477 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 478 |
ORDER BY g.graded_at DESC
|
| 479 |
LIMIT 10
|
| 480 |
"""),
|
|
|
|
| 403 |
scores_snapshot = scores_df.copy()
|
| 404 |
|
| 405 |
def _run() -> None:
|
| 406 |
+
fresh_conn = None
|
| 407 |
try:
|
| 408 |
from database.db import get_connection as _get_conn
|
| 409 |
fresh_conn = _get_conn()
|
| 410 |
on_game_complete(fresh_conn, int(pk_str), game_date, scores_snapshot)
|
| 411 |
except Exception as exc:
|
| 412 |
logger.warning("[_fire_completion] thread error game_pk=%s: %s", pk_str, exc)
|
| 413 |
+
finally:
|
| 414 |
+
if fresh_conn is not None:
|
| 415 |
+
try:
|
| 416 |
+
fresh_conn.close()
|
| 417 |
+
except Exception:
|
| 418 |
+
pass
|
| 419 |
|
| 420 |
t = threading.Thread(target=_run, daemon=True)
|
| 421 |
t.start()
|
|
|
|
| 482 |
SELECT 1 FROM statcast_event_core s
|
| 483 |
WHERE s.game_pk = g.game_pk::BIGINT
|
| 484 |
)
|
| 485 |
+
AND NOT EXISTS (
|
| 486 |
+
SELECT 1 FROM live_pitch_mix_2026 lpm
|
| 487 |
+
WHERE lpm.game_pk = g.game_pk::BIGINT
|
| 488 |
+
)
|
| 489 |
ORDER BY g.graded_at DESC
|
| 490 |
LIMIT 10
|
| 491 |
"""),
|
database/remote_db.py
CHANGED
|
@@ -29,6 +29,9 @@ def _get_engine():
|
|
| 29 |
|
| 30 |
_engine = create_engine(
|
| 31 |
database_url,
|
|
|
|
|
|
|
|
|
|
| 32 |
pool_pre_ping=True,
|
| 33 |
pool_recycle=300,
|
| 34 |
)
|
|
|
|
| 29 |
|
| 30 |
_engine = create_engine(
|
| 31 |
database_url,
|
| 32 |
+
pool_size=10,
|
| 33 |
+
max_overflow=20,
|
| 34 |
+
pool_timeout=60,
|
| 35 |
pool_pre_ping=True,
|
| 36 |
pool_recycle=300,
|
| 37 |
)
|