Files
new_lottery_bot/ASYNCIO_FIX_REPORT.md
2025-11-12 20:57:36 +09:00

4.0 KiB
Raw Blame History

Отчет об исправлении критической ошибки AsyncIO Event Loop

Проблема

При запуске бота в продакшене обнаружилась критическая ошибка:

RuntimeError: Task <Task pending> got Future <Future pending> attached to a different loop

Эта ошибка массово возникала у всех воркеров TaskManager (worker-0 до worker-14) и делала невозможным стабильную работу бота.

Причина

Проблема заключалась в том, что объекты asyncio.PriorityQueue() и asyncio.Semaphore() создавались в момент инициализации класса AsyncTaskManager, когда event loop еще не был запущен или был другой. Когда позже эти объекты использовались в основном event loop aiogram'а, возникал конфликт.

Проблемный код (до исправления):

def __init__(self, max_workers: int = 10, max_user_concurrent: int = 3):
    self.max_workers = max_workers
    self.max_user_concurrent = max_user_concurrent
    
    # ❌ ПРОБЛЕМА: создаём asyncio объекты в неправильном event loop
    self.task_queue = asyncio.PriorityQueue()
    self.worker_semaphore = asyncio.Semaphore(max_workers)
    self.user_semaphores: Dict[int, asyncio.Semaphore] = {}

Решение

Перенесли создание всех asyncio объектов в метод start(), который вызывается в правильном event loop:

Исправленный код:

def __init__(self, max_workers: int = 10, max_user_concurrent: int = 3):
    self.max_workers = max_workers
    self.max_user_concurrent = max_user_concurrent
    
    # ✅ ИСПРАВЛЕНО: объекты будут созданы при запуске
    self.task_queue: Optional[asyncio.PriorityQueue] = None
    self.worker_semaphore: Optional[asyncio.Semaphore] = None
    self.user_semaphores: Dict[int, asyncio.Semaphore] = {}

async def start(self):
    """Запуск менеджера задач"""
    if self.running:
        return
    
    # ✅ Создаём asyncio объекты в правильном event loop
    self.task_queue = asyncio.PriorityQueue()
    self.worker_semaphore = asyncio.Semaphore(self.max_workers)
    self.user_semaphores.clear()  # Очищаем старые семафоры
    
    self.running = True
    # ... остальная логика

Дополнительные исправления

  1. Проверки на None: Добавили проверки во все методы, чтобы убедиться, что asyncio объекты созданы
  2. Правильная очистка: В методе stop() очищаем все asyncio объекты
  3. Безопасное создание семафоров пользователей: Семафоры пользователей теперь также создаются в правильном event loop

Результат

  • Бот запускается без ошибок
  • Все 15 воркеров TaskManager работают корректно
  • Никаких RuntimeError с event loop
  • Корректное завершение работы по Ctrl+C

Файлы изменены

  • task_manager.py - основные исправления AsyncTaskManager

Тестирование

Создан тест, подтверждающий корректную работу TaskManager:

# Тест показал успешное выполнение:
# Статистика: {'running': True, 'workers_count': 15, 'active_tasks': 0, 
#              'queue_size': 0, 'completed_tasks': 1, 'failed_tasks': 0}

Бот успешно запускается и работает стабильно в продакшене.