"""LISTEN/NOTIFY 리스너 — 잡 적재 시 워커를 즉시 깨운다(폴링 낭비 제거). (LPS `worker/notify.py` 이식) 전용 asyncpg 연결로 LISTEN 한다(SQLAlchemy 풀과 분리). 알림이 오면 이벤트를 세팅하고, 워커는 claim 이 비었을 때 wait() 로 알림 또는 짧은 타임아웃(안전망/reaper)까지 대기한다. """ import asyncio import asyncpg from config.server_configs import main_db_config from crud.job_crud import JOB_NOTIFY_CHANNEL def _dsn() -> str: c = main_db_config pw = f":{c.write_pw}" if c.write_pw else "" return f"postgresql://{c.write_id}{pw}@{c.write_host}:{c.write_port}/{c.name}" class JobListener: """잡 적재 NOTIFY 를 받아 워커를 깨우는 전용 asyncpg LISTEN 연결(SQLAlchemy 풀과 분리).""" def __init__(self, channel: str = JOB_NOTIFY_CHANNEL): self._channel = channel self._conn: asyncpg.Connection | None = None self._event = asyncio.Event() async def start(self): self._conn = await asyncpg.connect(_dsn()) await self._conn.add_listener(self._channel, self._on_notify) def _on_notify(self, *_args): self._event.set() async def wait(self, timeout: float) -> bool: """알림이 오거나 timeout 까지 대기. 알림으로 깨면 True, 타임아웃이면 False.""" try: await asyncio.wait_for(self._event.wait(), timeout) return True except asyncio.TimeoutError: return False finally: self._event.clear() async def close(self): if self._conn is not None: await self._conn.close() self._conn = None