""" Async Worker Module Provides a persistent async worker thread for executing coroutines. Solves "Event loop is closed" errors by maintaining a single event loop. """ import asyncio import threading from typing import Any, Optional from concurrent.futures import Future import logging class AsyncWorker: """ Persistent async worker thread for executing coroutines. This worker maintains a single event loop for the application's lifetime, solving the "Event loop is closed" error by ensuring all async operations use the same loop and credentials remain valid. """ def __init__(self): self.loop: Optional[asyncio.AbstractEventLoop] = None self.thread: Optional[threading.Thread] = None self.running = False self.logger = logging.getLogger(__name__) def start(self): """Start the async worker thread.""" if self.running: return self.running = True self.thread = threading.Thread( target=self._run_loop, daemon=True, name="AsyncWorker" ) self.thread.start() # Wait for loop to be ready import time while self.loop is None: time.sleep(0.01) def _run_loop(self): """Run the event loop in the worker thread.""" self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.logger.info("AsyncWorker event loop started") try: self.loop.run_forever() finally: self.loop.close() self.logger.info("AsyncWorker event loop closed") def submit(self, coro) -> Future: """ Submit a coroutine to be executed in the worker loop. Args: coro: Coroutine to execute Returns: Future that will contain the result """ if not self.running: raise RuntimeError("AsyncWorker not started") result_future = Future() def callback(): """Execute coroutine and set result in future.""" try: task = asyncio.ensure_future(coro, loop=self.loop) def done_callback(task_future): try: result = task_future.result() result_future.set_result(result) except Exception as e: result_future.set_exception(e) task.add_done_callback(done_callback) except Exception as e: result_future.set_exception(e) self.loop.call_soon_threadsafe(callback) return result_future def stop(self): """Stop the async worker thread.""" if not self.running: return self.running = False if self.loop: self.loop.call_soon_threadsafe(self.loop.stop) if self.thread: self.thread.join(timeout=5.0) self.logger.info("AsyncWorker stopped")