diff --git a/meshchat.py b/meshchat.py index e7470d7..ca44997 100644 --- a/meshchat.py +++ b/meshchat.py @@ -2038,6 +2038,9 @@ class ReticulumMeshChat: # called when web app has started async def on_startup(app): + # remember main event loop + AsyncUtils.set_main_loop(asyncio.get_event_loop()) + # auto launch web browser if launch_browser: try: diff --git a/src/backend/async_utils.py b/src/backend/async_utils.py index d20d507..b8feaca 100644 --- a/src/backend/async_utils.py +++ b/src/backend/async_utils.py @@ -1,25 +1,25 @@ import asyncio +from typing import Coroutine class AsyncUtils: - # this method allows running the provided async coroutine from within a sync function - # it will run the async function on the existing event loop if available, otherwise it will start a new event loop + # remember main loop + main_loop: asyncio.AbstractEventLoop | None = None + @staticmethod - def run_async(coroutine): + def set_main_loop(loop: asyncio.AbstractEventLoop): + AsyncUtils.main_loop = loop - # attempt to get existing event loop - existing_event_loop = None - try: - existing_event_loop = asyncio.get_running_loop() - except RuntimeError: - # 'RuntimeError: no running event loop' - pass + # this method allows running the provided async coroutine from within a sync function + # it will run the async function on the main event loop if possible, otherwise it logs a warning + @staticmethod + def run_async(coroutine: Coroutine): - # if there is an existing event loop running, submit the coroutine to that loop - if existing_event_loop and existing_event_loop.is_running(): - existing_event_loop.create_task(coroutine) + # run provided coroutine on main event loop, ensuring thread safety + if AsyncUtils.main_loop and AsyncUtils.main_loop.is_running(): + asyncio.run_coroutine_threadsafe(coroutine, AsyncUtils.main_loop) return - # otherwise start a new event loop to run the coroutine - asyncio.run(coroutine) + # main event loop not running... + print("WARNING: Main event loop not available. Could not schedule task.")