diff --git a/bot.py b/bot.py index cf342507..ba210462 100644 --- a/bot.py +++ b/bot.py @@ -107,9 +107,6 @@ async def graceful_shutdown(): # sourcery skip: use-named-expression logger.info("麦麦优雅关闭完成") - # 关闭日志系统,释放文件句柄 - shutdown_logging() - except Exception as e: logger.error(f"麦麦关闭失败: {e}", exc_info=True) @@ -241,7 +238,7 @@ if __name__ == "__main__": # 确保 loop 在任何情况下都尝试关闭(如果存在且未关闭) if "loop" in locals() and loop and not loop.is_closed(): loop.close() - logger.info("事件循环已关闭") + print("[主程序] 事件循环已关闭") # 关闭日志系统,释放文件句柄 try: @@ -249,6 +246,8 @@ if __name__ == "__main__": except Exception as e: print(f"关闭日志系统时出错: {e}") - # 在程序退出前暂停,让你有机会看到输出 - # input("按 Enter 键退出...") # <--- 添加这行 - sys.exit(exit_code) # <--- 使用记录的退出码 + print("[主程序] 准备退出...") + + # 使用 os._exit() 强制退出,避免被阻塞 + # 由于已经在 graceful_shutdown() 中完成了所有清理工作,这是安全的 + os._exit(exit_code) diff --git a/src/common/logger.py b/src/common/logger.py index 4546e88a..55833c34 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -843,8 +843,8 @@ def start_log_cleanup_task(): def shutdown_logging(): """优雅关闭日志系统,释放所有文件句柄""" - logger = get_logger("logger") - logger.info("正在关闭日志系统...") + # 先输出到控制台,避免日志系统关闭后无法输出 + print("[logger] 正在关闭日志系统...") # 关闭所有handler root_logger = logging.getLogger() @@ -865,4 +865,5 @@ def shutdown_logging(): handler.close() logger_obj.removeHandler(handler) - logger.info("日志系统已关闭") + # 使用 print 而不是 logger,因为 logger 已经关闭 + print("[logger] 日志系统已关闭") diff --git a/src/common/server.py b/src/common/server.py index 140d86d1..ebdc9fa2 100644 --- a/src/common/server.py +++ b/src/common/server.py @@ -2,6 +2,7 @@ from fastapi import FastAPI, APIRouter from fastapi.middleware.cors import CORSMiddleware # 新增导入 from typing import Optional from uvicorn import Config, Server as UvicornServer +import asyncio import os from rich.traceback import install @@ -82,8 +83,17 @@ class Server: """安全关闭服务器""" if self._server: self._server.should_exit = True - await self._server.shutdown() - self._server = None + try: + # 添加 3 秒超时,避免 shutdown 永久挂起 + await asyncio.wait_for(self._server.shutdown(), timeout=3.0) + except asyncio.TimeoutError: + # 超时就强制标记为 None,让垃圾回收处理 + pass + except Exception: + # 忽略其他异常 + pass + finally: + self._server = None def get_app(self) -> FastAPI: """获取 FastAPI 实例""" diff --git a/src/webui/manager.py b/src/webui/manager.py index f4302c50..e4679f1e 100644 --- a/src/webui/manager.py +++ b/src/webui/manager.py @@ -41,8 +41,7 @@ def setup_production_mode() -> bool: """设置生产模式 - 挂载静态文件""" try: from src.common.server import get_global_server - from fastapi.staticfiles import StaticFiles - from fastapi.responses import FileResponse + from starlette.responses import FileResponse server = get_global_server() base_dir = Path(__file__).parent.parent.parent @@ -58,14 +57,6 @@ def setup_production_mode() -> bool: logger.warning("💡 请确认前端已正确构建") return False - # 挂载静态资源 - if (static_path / "assets").exists(): - server.app.mount( - "/assets", - StaticFiles(directory=str(static_path / "assets")), - name="assets" - ) - # 处理 SPA 路由 @server.app.get("/{full_path:path}") async def serve_spa(full_path: str): @@ -77,6 +68,7 @@ def setup_production_mode() -> bool: # 检查文件是否存在 file_path = static_path / full_path if file_path.is_file(): + # 直接返回文件,Starlette 会自动管理文件句柄 return FileResponse(file_path) # 返回 index.html(SPA 路由)