Django与Aiogram Webhook集成执行migrate命令时出现RuntimeError问题求助
Django与Aiogram Webhook集成执行migrate命令时出现RuntimeError问题求助
问题描述
我在尝试将Aiogram开发的Telegram机器人通过Webhook与Django应用集成时遇到了棘手的问题。每次执行python manage.py migrate命令时,控制台都会抛出以下错误:
RuntimeError: Event loop is closed Unclosed client session
机器人的Webhook处理逻辑是在Django的同步视图中通过asyncio执行异步任务的。我试过用ChatGPT排查,但没找到有效的解决方法,相关场景和代码如下:
错误场景与代码
我的Django项目中,Webhook视图的核心代码是这样的:
session = AiohttpSession() bot = Bot(token=settings.BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) dp = Dispatcher() @csrf_exempt def webhook(request): if request.method == "POST": try: update_data = json.loads(request.body) update = Update(**update_data) async def process_update(): await dp.feed_update(bot=bot, update=update) loop = asyncio.get_event_loop() if loop.is_closed(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(process_update()) return HttpResponse("OK") except Exception as e: logger.error(f"Error: {e}") return HttpResponse("Bad Request", status=400) return HttpResponse("Method not allowed", status=405)
奇怪的是,即使Webhook还没被任何请求调用,执行migrate时就会触发事件循环关闭和未关闭客户端会话的错误。
问题根源
- 模块级异步资源提前初始化:你在代码模块的顶层直接创建了
AiohttpSession和Bot实例。当Django启动执行migrate时,这些异步对象会被自动初始化,但migrate是纯同步命令,执行完成后不会正确关闭这些异步资源,导致进程退出时抛出资源未释放的警告和错误。 - 事件循环管理不规范:手动获取/重建全局事件循环的方式存在风险,容易出现循环状态不一致的情况,尤其是在Django的同步运行环境下。
解决方案
下面提供两种可行的修复方案,你可以根据自己的需求选择:
方案一:懒加载Bot与Session(快速简单)
将Bot和Session的初始化逻辑移到Webhook视图内部,仅在处理请求时才创建,避免migrate命令触发异步资源的初始化:
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseMethodNotAllowed from django.views.decorators.csrf import csrf_exempt import json import asyncio from aiogram import Bot, Dispatcher, Update from aiogram.client.session.aiohttp import AiohttpSession from aiogram.enums import ParseMode from aiogram.types import DefaultBotProperties from django.conf import settings import logging logger = logging.getLogger(__name__) # 仅将Dispatcher保留在模块级别(因为它需要存储机器人的处理逻辑) dp = Dispatcher() @csrf_exempt def webhook(request): if request.method != "POST": return HttpResponseMethodNotAllowed(["POST"]) try: update_data = json.loads(request.body) update = Update(**update_data) # 仅在处理请求时创建Bot和Session async def process_update(): session = AiohttpSession() bot = Bot( token=settings.BOT_TOKEN, session=session, default=DefaultBotProperties(parse_mode=ParseMode.HTML) ) try: await dp.feed_update(bot=bot, update=update) finally: # 确保会话被正确关闭,避免资源泄漏 await session.close() await bot.session.close() # 使用asyncio.run自动管理事件循环(创建、运行、关闭全流程) asyncio.run(process_update()) return HttpResponse("OK") except Exception as e: logger.error(f"处理Webhook时出错: {e}") return HttpResponseBadRequest("Bad Request")
方案二:单例模式复用Bot与Session(性能更优)
如果希望复用Bot和Session以提升性能,可以使用单例模式懒加载,并通过Django信号在应用关闭时正确清理异步资源:
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseMethodNotAllowed from django.views.decorators.csrf import csrf_exempt from django.core.signals import app_shutdown import json import asyncio import threading from aiogram import Bot, Dispatcher, Update from aiogram.client.session.aiohttp import AiohttpSession from aiogram.enums import ParseMode from aiogram.types import DefaultBotProperties from django.conf import settings import logging logger = logging.getLogger(__name__) dp = Dispatcher() _bot_instance = None _session_instance = None _instance_lock = threading.Lock() def get_bot(): """懒加载Bot实例,确保全局唯一""" global _bot_instance, _session_instance with _instance_lock: if _bot_instance is None or _session_instance.closed: _session_instance = AiohttpSession() _bot_instance = Bot( token=settings.BOT_TOKEN, session=_session_instance, default=DefaultBotProperties(parse_mode=ParseMode.HTML) ) return _bot_instance def close_bot_session(sender, **kwargs): """应用关闭时清理Bot会话的信号处理器""" global _bot_instance, _session_instance if _bot_instance is not None: # 在单独线程中执行异步关闭操作(因为信号处理器是同步的) def cleanup_async(): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(_session_instance.close()) loop.run_until_complete(_bot_instance.session.close()) loop.close() threading.Thread(target=cleanup_async).start() # 注册应用关闭信号 app_shutdown.connect(close_bot_session) @csrf_exempt def webhook(request): if request.method != "POST": return HttpResponseMethodNotAllowed(["POST"]) try: update_data = json.loads(request.body) update = Update(**update_data) bot = get_bot() async def process_update(): await dp.feed_update(bot=bot, update=update) asyncio.run(process_update()) return HttpResponse("OK") except Exception as e: logger.error(f"处理Webhook时出错: {e}") return HttpResponseBadRequest("Bad Request")
修复原理
- 避免migrate时初始化异步资源:通过懒加载逻辑,只有当第一个Webhook请求到达时才会创建Bot和Session,migrate命令执行时不会触发这些异步对象的初始化,自然不会出现资源未关闭的问题。
- 规范事件循环管理:使用
asyncio.run()替代手动操作事件循环,它会自动创建新的事件循环、执行异步任务并在完成后关闭循环,彻底解决"Event loop is closed"错误。 - 资源主动清理:无论是每次请求后关闭会话,还是在应用 shutdown 时统一清理,都确保了aiohttp会话被正确关闭,消除"Unclosed client session"警告。
备注:内容来源于stack exchange,提问作者Abdulaziz coder
相关产品推荐
相关产品推荐

