109 lines
2.9 KiB
Python
109 lines
2.9 KiB
Python
"""
|
|
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")
|