"""Feishu outbox draining loops for app lifecycle.""" from __future__ import annotations import asyncio from contextlib import suppress import sqlalchemy as sa from ..logging import get_logger from ..database import engine from ...services.feishu_integration_service import dispatch_pending_feishu_replies from .db_guard import normalize_dialect logger = get_logger(__name__) def is_postgresql_runtime() -> bool: return normalize_dialect(engine.dialect.name) == "postgresql" async def feishu_outbox_polling_loop() -> None: while True: try: dispatched = await dispatch_pending_feishu_replies(limit=50) await asyncio.sleep(0.3 if dispatched > 0 else 2.0) except asyncio.CancelledError: break except Exception as exc: logger.warning("feishu_outbox_drain_failed", error=str(exc)) await asyncio.sleep(2.0) async def feishu_outbox_listen_loop() -> None: while True: listener_callback = None driver_connection = None try: async with engine.connect() as conn: await conn.execute(sa.text("LISTEN feishu_outbox_ready")) await conn.commit() raw_conn = await conn.get_raw_connection() driver_connection = getattr(raw_conn, "driver_connection", None) if driver_connection is None or not hasattr(driver_connection, "add_listener"): logger.warning( "feishu_outbox_listen_unsupported_driver", driver=str(type(driver_connection)), ) await feishu_outbox_polling_loop() return notify_queue: asyncio.Queue[str] = asyncio.Queue(maxsize=1024) def _on_notify(_connection, _pid, _channel, payload) -> None: try: notify_queue.put_nowait(str(payload or "")) except asyncio.QueueFull: pass listener_callback = _on_notify await driver_connection.add_listener("feishu_outbox_ready", listener_callback) logger.info("feishu_outbox_listen_ready") while True: dispatched = await dispatch_pending_feishu_replies(limit=50) if dispatched > 0: continue try: await asyncio.wait_for(notify_queue.get(), timeout=2.0) while not notify_queue.empty(): notify_queue.get_nowait() except asyncio.TimeoutError: continue except asyncio.CancelledError: break except Exception as exc: logger.warning("feishu_outbox_listen_failed", error=str(exc)) await asyncio.sleep(2.0) finally: if driver_connection is not None and listener_callback is not None: with suppress(Exception): await driver_connection.remove_listener("feishu_outbox_ready", listener_callback) async def feishu_outbox_drain_loop() -> None: if is_postgresql_runtime(): await feishu_outbox_listen_loop() return await feishu_outbox_polling_loop()