This commit is contained in:
2025-10-27 18:40:31 +03:00
parent b0aba27764
commit 4048f8fdfa

View File

@@ -361,38 +361,36 @@ def get_interval_by_name(name: str) -> int:
def run_live_loop( def run_live_loop(
parent_session_unused,
league: str, league: str,
season: str, season: str,
game_id: int, game_id: int,
lang: str, lang: str,
game_meta: dict, game_meta: dict,
stop_event: threading.Event,
): ):
""" """
Поток, который дергает онлайн API матча. Запускает два рабочих цикла:
По завершении матча говорит рендеру остановиться. - poll_game_live (опрашивает API матча)
- render_loop (собирает ui_state.json)
Управляет их остановкой.
""" """
logger.info( logger.info(
f"[LIVE_THREAD] start live loop for game_id={game_id} (league={league}, season={season})" f"[LIVE_THREAD] start live loop for game_id={game_id} (league={league}, season={season})"
) )
# создаём свою сессию, чтобы не делить session между потоками # отдельная сессия только для лайва
session = create_session() session = create_session()
# общий stop_event для live и render # поток рендера
stop_event = threading.Event()
# запускаем рендер-поток
render_thread = threading.Thread( render_thread = threading.Thread(
target=render_loop, target=render_loop,
args=(stop_event,), # только stop_event, out_name используем дефолт "ui_state" args=(stop_event,), # дефолт out_name="game" в твоём render_loop можно оставить
daemon=True, daemon=False,
) )
render_thread.start() render_thread.start()
logger.info("[LIVE_THREAD] render thread spawned") logger.info("[LIVE_THREAD] render thread spawned")
try: try:
# крутим опрос API до конца матча
poll_game_live( poll_game_live(
session=session, session=session,
league=league, league=league,
@@ -400,12 +398,15 @@ def run_live_loop(
game_id=game_id, game_id=game_id,
lang=lang, lang=lang,
game_meta=game_meta, game_meta=game_meta,
stop_event=stop_event,
) )
except Exception as e: except Exception as e:
logger.exception(f"[LIVE_THREAD] crash in live loop for game_id={game_id}: {e}") logger.exception(f"[LIVE_THREAD] crash in live loop for game_id={game_id}: {e}")
finally: finally:
# матч кончился -> выключаем рендер # какое бы ни было завершение, просим рендер остановиться
stop_event.set() stop_event.set()
logger.info(f"[LIVE_THREAD] stopping render thread for game_id={game_id}")
render_thread.join()
logger.info(f"[LIVE_THREAD] stop live loop for game_id={game_id}") logger.info(f"[LIVE_THREAD] stop live loop for game_id={game_id}")
@@ -418,30 +419,20 @@ def poll_game_live(
game_meta: dict, game_meta: dict,
stop_event: threading.Event, stop_event: threading.Event,
): ):
""" slow_endpoints = ["game"] # "pregame-fullstats" можно добавить обратно
Онлайн-цикл:
- "game" раз в 600 сек (pregame-fullstats можно вернуть позже)
- "live-status", "box-score", "play-by-play" раз в 1 сек
Цикл выходит, когда матч перестаёт быть live ИЛИ когда сработал stop_event.
"""
slow_endpoints = ["game"]
fast_endpoints = ["live-status", "box-score", "play-by-play"] fast_endpoints = ["live-status", "box-score", "play-by-play"]
last_call = {ep: 0 for ep in slow_endpoints + fast_endpoints} last_call = {name: 0 for name in slow_endpoints + fast_endpoints}
# пул потоков живет весь матч
with ThreadPoolExecutor(max_workers=5) as executor: with ThreadPoolExecutor(max_workers=5) as executor:
while True: while True:
# внешняя принудительная остановка # внешний стоп с клавиатуры / по команде
if stop_event.is_set(): if stop_event.is_set():
logger.info(f"[LIVE_LOOP] stop_event set -> break live poll for game {game_id}") logger.info(f"[POLL] stop_event set -> break live poll for game {game_id}")
break break
now = time.time() now = time.time()
# какие ручки надо дёрнуть прямо сейчас
to_run = [] to_run = []
for ep in fast_endpoints + slow_endpoints: for ep in fast_endpoints + slow_endpoints:
interval = get_interval_by_name(ep) interval = get_interval_by_name(ep)
@@ -464,7 +455,6 @@ def poll_game_live(
) )
game_finished = False game_finished = False
for fut in as_completed(futures): for fut in as_completed(futures):
try: try:
ep_name, data = fut.result() ep_name, data = fut.result()
@@ -486,7 +476,6 @@ def poll_game_live(
except Exception as e: except Exception as e:
logger.exception(f"poll endpoint error: {e}") logger.exception(f"poll endpoint error: {e}")
# страховка по календарю (game_meta мог устареть, но лучше чем ничего)
if not is_game_live(game_meta): if not is_game_live(game_meta):
logger.info( logger.info(
f"Game {game_id} no longer live by calendar meta -> stop loop" f"Game {game_id} no longer live by calendar meta -> stop loop"
@@ -497,10 +486,14 @@ def poll_game_live(
break break
time.sleep(0.2) time.sleep(0.2)
logger.debug("live poll tick ok")
# ещё одна точка выхода даже если статус не изменился
if stop_event.is_set():
logger.info(f"[POLL] stop_event set after sleep -> break live poll for game {game_id}")
break
def get_data_API(session, league: str, team: str, lang: str): def get_data_API(session, league: str, team: str, lang: str, stop_event: threading.Event):
json_seasons = fetch_api_data( json_seasons = fetch_api_data(
session, "seasons", host=HOST, league=league, lang=lang session, "seasons", host=HOST, league=league, lang=lang
) )
@@ -510,7 +503,6 @@ def get_data_API(session, league: str, team: str, lang: str):
season = json_seasons[0]["season"] season = json_seasons[0]["season"]
# standings и calendar просто кешируем в файлы
fetch_api_data( fetch_api_data(
session, "standings", host=HOST, league=league, season=season, lang=lang session, "standings", host=HOST, league=league, season=season, lang=lang
) )
@@ -523,71 +515,35 @@ def get_data_API(session, league: str, team: str, lang: str):
today_game, last_played = get_game_id(json_calendar, team) today_game, last_played = get_game_id(json_calendar, team)
# если есть завершённая последняя игра — просто сохраним статический срез и выходим
if last_played and not today_game: if last_played and not today_game:
game_id = last_played["game"]["id"] game_id = last_played["game"]["id"]
logger.info(f"Последний завершённый матч id={game_id}") logger.info(f"Последний завершённый матч id={game_id}")
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang) fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
return return
# если есть матч сегодня
if today_game: if today_game:
game_id = today_game["game"]["id"] game_id = today_game["game"]["id"]
logger.info(f"Онлайн матч id={game_id}") logger.info(f"Онлайн матч id={game_id}")
# базовые данные прямо сейчас
fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang) fetch_api_data(session, "game", host=HOST, game_id=game_id, lang=lang)
# если матч реально идёт -> запускаем live-петлю и рендер
if is_game_live(today_game["game"]): if is_game_live(today_game["game"]):
# отдельная сессия для live-пула (как раньше) t = threading.Thread(
live_session = create_session() target=run_live_loop,
args=(league, season, game_id, lang, today_game["game"], stop_event),
# единый stop_event для всего матча
stop_event = threading.Event()
# поток рендера
render_thread = threading.Thread(
target=render_loop,
args=(stop_event, "ui_state"), # имя файла можешь менять
daemon=False, daemon=False,
) )
render_thread.start() t.start()
logger.info("[MAIN] render thread spawned") logger.info("live thread spawned, waiting for it to finish...")
# поток live-пулинга API try:
def live_worker(): t.join()
try: except KeyboardInterrupt:
poll_game_live( logger.info("KeyboardInterrupt while waiting live thread -> stop_event")
session=live_session, stop_event.set()
league=league, t.join()
season=season,
game_id=game_id,
lang=lang,
game_meta=today_game["game"],
stop_event=stop_event,
)
except Exception as e:
logger.exception(f"[LIVE_THREAD] crash in live loop: {e}")
live_thread = threading.Thread(
target=live_worker,
daemon=False,
)
live_thread.start()
logger.info("[MAIN] live thread spawned")
# дожидаемся окончания live_thread (то есть завершения матча или ошибки)
live_thread.join()
logger.info("[MAIN] live thread finished")
# говорим рендеру остановиться
stop_event.set()
# ждём корректного завершения рендера
render_thread.join()
logger.info("[MAIN] render thread finished")
logger.info("live thread finished")
return return
logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.") logger.info("Для этой команды игр сегодня нет и нет завершённой последней игры.")