90 lines
3.3 KiB
Python
90 lines
3.3 KiB
Python
"""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()
|