eigent/backend/main.py
2025-11-08 14:48:38 +08:00

133 lines
4.2 KiB
Python

import os
import sys
import pathlib
import signal
import asyncio
import atexit
# Add project root to Python path to import shared utils
_project_root = pathlib.Path(__file__).parent.parent
if str(_project_root) not in sys.path:
sys.path.insert(0, str(_project_root))
# 1) Load env and init traceroot BEFORE importing modules that get a logger
from utils import traceroot_wrapper as traceroot
from app import api
# Only initialize traceroot if enabled
if traceroot.is_enabled():
from traceroot.integrations.fastapi import connect_fastapi
connect_fastapi(api)
# 2) Now safe to import modules that use traceroot.get_logger() at import-time
from app.component.environment import env
from app.router import register_routers
os.environ["PYTHONIOENCODING"] = "utf-8"
app_logger = traceroot.get_logger("main")
# Log application startup
app_logger.info("Starting Eigent Multi-Agent System API")
app_logger.info(f"Python encoding: {os.environ.get('PYTHONIOENCODING')}")
app_logger.info(f"Environment: {os.environ.get('ENVIRONMENT', 'development')}")
prefix = env("url_prefix", "")
app_logger.info(f"Loading routers with prefix: '{prefix}'")
register_routers(api, prefix)
app_logger.info("All routers loaded successfully")
# Check if debug mode is enabled via environment variable
if os.environ.get('ENABLE_PYTHON_DEBUG') == 'true':
try:
import debugpy
DEBUG_PORT = int(os.environ.get('DEBUG_PORT', '5678'))
app_logger.info(f"Debug mode enabled - Starting debugpy server on port {DEBUG_PORT}")
debugpy.listen(("localhost", DEBUG_PORT))
app_logger.info(f"Debugger ready for attachment on localhost:{DEBUG_PORT}")
#📝 In VS Code: Run 'Debug Python Backend (Attach)' configuration
# Don't wait for client automatically - let it attach when ready
except ImportError:
app_logger.warning("debugpy not available, install with: uv add debugpy")
except Exception as e:
app_logger.error(f"Failed to start debugpy: {e}")
dir = pathlib.Path(__file__).parent / "runtime"
dir.mkdir(parents=True, exist_ok=True)
# Write PID file asynchronously
async def write_pid_file():
r"""Write PID file asynchronously"""
import aiofiles
async with aiofiles.open(dir / "run.pid", "w") as f:
await f.write(str(os.getpid()))
app_logger.info(f"PID file written: {os.getpid()}")
# Create task to write PID
pid_task = asyncio.create_task(write_pid_file())
app_logger.info("PID write task created")
# Graceful shutdown handler
shutdown_event = asyncio.Event()
async def cleanup_resources():
r"""Cleanup all resources on shutdown"""
app_logger.info("Starting graceful shutdown process")
from app.service.task import task_locks, _cleanup_task
if _cleanup_task and not _cleanup_task.done():
_cleanup_task.cancel()
try:
await _cleanup_task
except asyncio.CancelledError:
pass
# Cleanup all task locks
for task_id in list(task_locks.keys()):
try:
task_lock = task_locks[task_id]
await task_lock.cleanup()
except Exception as e:
app_logger.error(f"Error cleaning up task {task_id}: {e}")
# Remove PID file
pid_file = dir / "run.pid"
if pid_file.exists():
pid_file.unlink()
app_logger.info("All resources cleaned up successfully")
def signal_handler(signum, frame):
r"""Handle shutdown signals"""
app_logger.warning(f"Received shutdown signal: {signum}")
asyncio.create_task(cleanup_resources())
shutdown_event.set()
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler)
# Register cleanup on exit with safe synchronous wrapper
def sync_cleanup():
"""Synchronous cleanup for atexit - handles PID file removal"""
try:
# Only perform synchronous cleanup tasks
pid_file = dir / "run.pid"
if pid_file.exists():
pid_file.unlink()
app_logger.info("PID file removed during shutdown")
except Exception as e:
app_logger.error(f"Error during atexit cleanup: {e}")
atexit.register(sync_cleanup)
# Log successful initialization
app_logger.info("Application initialization completed successfully")