"""Channel worker entrypoint with embedded health endpoint.""" from __future__ import annotations import asyncio import os import signal import sys if sys.platform == "win32": sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) from aiohttp import web from yuxi.channel.infrastructure.agent.agent_adapter import AgentAdapter from yuxi.channel.startup import init_channel, shutdown_channel from yuxi.storage.postgres.manager import pg_manager from yuxi.utils.logging_config import logger _HEALTH_PORT = int(os.getenv("CHANNEL_HEALTH_PORT", "5051")) async def _health_handler(request: web.Request) -> web.Response: checks: dict[str, str] = {"status": "ok"} container = request.app["container"] try: redis = container.cache_port await redis.ping() checks["redis"] = "ok" except Exception as e: checks["redis"] = f"error: {e}" checks["status"] = "degraded" worker_pool = getattr(container, "worker_pool", None) if worker_pool and getattr(worker_pool, "is_running", False): checks["worker_pool"] = "ok" else: checks["worker_pool"] = "not_running" checks["status"] = "degraded" status_code = 200 if checks["status"] == "ok" else 503 return web.json_response(checks, status=status_code) async def _start_health_server(container: object) -> web.AppRunner: app = web.Application() app["container"] = container app.router.add_get("/healthz", _health_handler) runner = web.AppRunner(app) await runner.setup() site = web.TCPSite(runner, "0.0.0.0", _HEALTH_PORT) await site.start() logger.info("health endpoint started on port %d", _HEALTH_PORT) return runner async def main(): redis_url = os.getenv("REDIS_URL", "redis://redis:6379/0") config_path = os.getenv("CHANNEL_CONFIG_PATH", "channel_config.yaml") logger.info("starting channel worker, redis=%s", redis_url) container = await init_channel( redis_url=redis_url, agent_port=AgentAdapter(pg_manager.get_async_session_context), config_yaml_path=config_path, ) health_runner = await _start_health_server(container) stop_event = asyncio.Event() def _signal_handler(): stop_event.set() loop = asyncio.get_running_loop() if sys.platform == "win32": for sig in (signal.SIGINT, signal.SIGTERM): signal.signal(sig, lambda *_: _signal_handler()) else: for sig in (signal.SIGINT, signal.SIGTERM): loop.add_signal_handler(sig, _signal_handler) logger.info("channel worker running, waiting for stop signal") await stop_event.wait() await health_runner.cleanup() logger.info("health endpoint stopped") await shutdown_channel(container) logger.info("channel worker stopped") if __name__ == "__main__": asyncio.run(main())