"""LISTEN/NOTIFY 리스너 — 잡 적재 시 워커를 즉시 깨운다(폴링 낭비 제거).""" 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 까지 대기.""" 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