First commit
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
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")
|
||||
Reference in New Issue
Block a user