# ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= import asyncio import threading from collections.abc import Coroutine from typing import Any class AsyncRunner: def __init__(self) -> None: self._loop: asyncio.AbstractEventLoop | None = None self._thread: threading.Thread | None = None self._lock = threading.Lock() def _ensure_loop(self) -> asyncio.AbstractEventLoop: with self._lock: if self._loop is not None and not self._loop.is_closed(): return self._loop self._loop = asyncio.new_event_loop() self._thread = threading.Thread( target=self._loop.run_forever, name="mirage-camel-runner", daemon=True, ) self._thread.start() return self._loop def run(self, coro: Coroutine[Any, Any, Any]) -> Any: loop = self._ensure_loop() future = asyncio.run_coroutine_threadsafe(coro, loop) return future.result() def close(self) -> None: with self._lock: if self._loop is None: return loop = self._loop self._loop = None loop.call_soon_threadsafe(loop.stop) if self._thread is not None: self._thread.join(timeout=2.0) self._thread = None loop.close()