fix(generator): устранён флаки runtime-гейта стыка backfill/live
- Зачем:
- гейт generated-history-runtime-check падал через раз: docker compose
stop убивал генератор SIGKILL'ом посреди batch (SIGTERM не ловился),
непарные строки маскировались под «смену фактуры» (задача 20).
- Что:
- генератор грациозно завершается по SIGTERM: текущий batch дописывается
во все топики с flush и записью history; compose даёт минуту grace.
- runtime-check ждёт пересекающий визит в STG (предусловие проверки),
seam-check получил precheck непарных live-строк; в STG-запросах
закреплён 'UTC' против сдвига наивных меток в поясе сервера.
- контрактные тесты усилены, задача 20 закрыта, блокер задачи 13 снят.
- Проверка:
- тесты: 192 passed (generator), 21 passed (контракты);
- стенд: 3 подряд зелёных make generated-history-runtime-check;
красный сценарий (искажение referer_url пересекающего визита) валит
гейт прежним сообщением при нулевых непарных счётчиках.
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
"""Основной сервисный цикл генератора."""
|
||||
|
||||
import logging
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
import uuid
|
||||
from datetime import datetime, timedelta, timezone
|
||||
@@ -54,6 +56,8 @@ class GeneratorService:
|
||||
self.state_manager: KafkaStateManager | None = None
|
||||
self.manifest_manager: KafkaStartupHistoryManifest | None = None
|
||||
self._running = False
|
||||
self._stop_requested = False
|
||||
self._shutdown_event = threading.Event()
|
||||
self._tick = 0
|
||||
self._model_time = config.model_t0
|
||||
|
||||
@@ -168,7 +172,7 @@ class GeneratorService:
|
||||
else:
|
||||
logger.info("State management disabled")
|
||||
|
||||
self._running = True
|
||||
self._running = not self._stop_requested
|
||||
|
||||
try:
|
||||
self._main_loop()
|
||||
@@ -181,6 +185,7 @@ class GeneratorService:
|
||||
"""Останавливает сервис."""
|
||||
logger.info("Stopping generator service...")
|
||||
self._running = False
|
||||
self._shutdown_event.set()
|
||||
if self.publisher:
|
||||
self.publisher.close()
|
||||
if self.history:
|
||||
@@ -190,6 +195,13 @@ class GeneratorService:
|
||||
if self.manifest_manager:
|
||||
self.manifest_manager.close()
|
||||
|
||||
def request_stop(self, signum, _frame) -> None:
|
||||
"""Просит завершить сервис после текущего batch."""
|
||||
logger.info("Received shutdown signal %s", signum)
|
||||
self._stop_requested = True
|
||||
self._running = False
|
||||
self._shutdown_event.set()
|
||||
|
||||
def restore_from_startup_history(
|
||||
self,
|
||||
state,
|
||||
@@ -642,7 +654,7 @@ class GeneratorService:
|
||||
sleep_time = max(0, self.config.tick_seconds - elapsed)
|
||||
if sleep_time > 0:
|
||||
logger.debug(f"Sleeping for {sleep_time:.1f}s until next tick")
|
||||
time.sleep(sleep_time)
|
||||
self._shutdown_event.wait(sleep_time)
|
||||
|
||||
def _advance_model_time(self) -> None:
|
||||
"""Сдвигает модельное время после успешного live-тика."""
|
||||
@@ -656,6 +668,7 @@ def main():
|
||||
try:
|
||||
config = Config()
|
||||
service = GeneratorService(config)
|
||||
signal.signal(signal.SIGTERM, service.request_stop)
|
||||
service.start()
|
||||
except Exception as e:
|
||||
logger.exception(f"Fatal error: {e}")
|
||||
|
||||
Reference in New Issue
Block a user