fix(scheduler): per-connector hard timeout + reorder mangoporn-first
Bug-report 2026-05-30 "ingest znów się zawiesił". streamporn/pandamovies wieszały się intermittentnie mid-run (zależnie od live-contentu danego dnia), blokując sekwencyjny _job_movie_ingest → mangoporn (jedyny mirror z realnym new-content: 72 nowych 05-28) nigdy nie startował. try/except chronił przed wyjątkiem, NIE przed hangiem. Fix: - _job_movie_ingest: każdy connector w ThreadPoolExecutor z future.result (timeout=360s). Hang jednego źródła → log + shutdown(wait=False) + kolejka leci dalej. Healthy run ~50s, cap 6min = zapas. - get_movie_connectors: reorder paradisehill, MANGOPORN, streamporn, pandamovies — mangoporn zaraz po canonical primary, przed wolniejszymi/wieszającymi się. Zweryfikowane: pełny _job_movie_ingest przeszedł wszystkie 4 success w nowej kolejności (mangoporn 2nd, 23s). 33 osierocone "running" rows (worker ubity mid-run przy deployach) wyczyszczone osobno. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c19da51aff
commit
05c0f6ef93
2 changed files with 31 additions and 2 deletions
|
|
@ -40,9 +40,15 @@ def get_movie_connectors() -> list[tuple[str, type]]:
|
||||||
)
|
)
|
||||||
from app.connectors.paradisehill import ParadisehillConnector
|
from app.connectors.paradisehill import ParadisehillConnector
|
||||||
|
|
||||||
|
# Kolejność ingestu: paradisehill FIRST (canonical primary, mirrory się do
|
||||||
|
# niego przyklejają), potem mangoporn (jedyny mirror z realnym new-content —
|
||||||
|
# 72 nowych 2026-05-28; streamporn/pandamovies zwracają stale 0 new), na końcu
|
||||||
|
# streamporn + pandamovies. Powód reorderu (2026-05-30): gdy streamporn wiesza
|
||||||
|
# się intermittentnie, mangoporn musi zdążyć przed nim — patrz per-connector
|
||||||
|
# timeout w _job_movie_ingest.
|
||||||
return [
|
return [
|
||||||
("paradisehill", ParadisehillConnector),
|
("paradisehill", ParadisehillConnector),
|
||||||
|
("mangoporn", MangopornConnector),
|
||||||
("streamporn", StreampornConnector),
|
("streamporn", StreampornConnector),
|
||||||
("pandamovies", PandamoviesConnector),
|
("pandamovies", PandamoviesConnector),
|
||||||
("mangoporn", MangopornConnector),
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -98,11 +98,34 @@ def _job_movie_ingest() -> None:
|
||||||
Kolejność: paradisehill FIRST (żeby mirrory miały do czego się przykleić),
|
Kolejność: paradisehill FIRST (żeby mirrory miały do czego się przykleić),
|
||||||
potem mirrory. Pojedynczy failed connector NIE zatrzymuje pozostałych —
|
potem mirrory. Pojedynczy failed connector NIE zatrzymuje pozostałych —
|
||||||
każdy w osobnym try/except.
|
każdy w osobnym try/except.
|
||||||
|
|
||||||
|
HARD TIMEOUT per-connector (bug-report 2026-05-30 "ingest znów się zawiesił"):
|
||||||
|
sam try/except chroni przed *wyjątkiem*, ale NIE przed *hangiem* (CPU-bound
|
||||||
|
ReDoS na patologicznej stronie / thread-stall) — wtedy jeden mirror blokuje
|
||||||
|
resztę i mangoporn (jedyny z realnym new-content) nigdy nie startuje.
|
||||||
|
Każdy connector leci w osobnym wątku z `future.result(timeout)`; po
|
||||||
|
przekroczeniu logujemy i idziemy dalej (osierocony wątek dożywa do restartu
|
||||||
|
workera — OK, bo loop się odblokowuje). Healthy run ~50s, cap 6 min = zapas.
|
||||||
"""
|
"""
|
||||||
|
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FutureTimeout
|
||||||
|
|
||||||
|
PER_CONNECTOR_TIMEOUT = 360 # sekundy
|
||||||
|
|
||||||
for name, cls in get_movie_connectors():
|
for name, cls in get_movie_connectors():
|
||||||
log.info("[scheduler] movie ingest %s starting", name)
|
log.info("[scheduler] movie ingest %s starting", name)
|
||||||
try:
|
try:
|
||||||
ingest_movies_from_connector(cls(), use_delta=True)
|
ex = ThreadPoolExecutor(max_workers=1)
|
||||||
|
fut = ex.submit(ingest_movies_from_connector, cls(), use_delta=True)
|
||||||
|
try:
|
||||||
|
fut.result(timeout=PER_CONNECTOR_TIMEOUT)
|
||||||
|
except FutureTimeout:
|
||||||
|
log.error(
|
||||||
|
"[scheduler] movie ingest %s HUNG > %ds — skip, kolejka leci dalej",
|
||||||
|
name, PER_CONNECTOR_TIMEOUT,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
# shutdown(wait=False): nie blokuj na join osieroconego wątku.
|
||||||
|
ex.shutdown(wait=False)
|
||||||
except Exception:
|
except Exception:
|
||||||
log.exception("[scheduler] movie ingest %s failed", name)
|
log.exception("[scheduler] movie ingest %s failed", name)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue