
    [jz                        d Z ddlmZ ddlZddlZddlmZ ddlmZm	Z	m
Z
  ej        e          Zddej        dddZddZdS )u  Async/sync bridging helpers.

The codebase has ~30 sites that schedule a coroutine onto an event loop from a
worker thread via :func:`asyncio.run_coroutine_threadsafe`.  That function can
raise :class:`RuntimeError` (e.g. the loop was closed during a shutdown race),
and when it does the coroutine object is never awaited and never closed —
which triggers a ``"coroutine '<name>' was never awaited"`` RuntimeWarning and
leaks the coroutine's frame until GC.

:func:`safe_schedule_threadsafe` wraps the call, closes the coroutine on
scheduling failure, and returns ``None`` (instead of a half-formed future) so
callers can branch cleanly:

    fut = safe_schedule_threadsafe(coro, loop)
    if fut is None:
        return  # or fallback behavior
    fut.result(timeout=5)

The helper deliberately does NOT also handle ``future.result()`` failures —
that is a separate concern.  Once the loop has accepted the coroutine, its
lifecycle belongs to the loop, not the scheduling thread.
    )annotationsN)Future)Any	CoroutineOptionalz$Failed to schedule coroutine on loop)loggerlog_message	log_levelcoroCoroutine[Any, Any, Any]loop#Optional[asyncio.AbstractEventLoop]r   Optional[logging.Logger]r	   strr
   intreturnOptional[Future]c               z   ||nt           }|At          j        |           r|                                  |                    |d|           dS 	 t          j        | |          S # t          $ rK}t          j        |           r|                                  |                    |d||           Y d}~dS d}~ww xY w)aY  Schedule ``coro`` on ``loop`` from a sync context, leak-safe.

    Returns the :class:`concurrent.futures.Future` on success, or ``None`` if
    the loop is missing or :func:`asyncio.run_coroutine_threadsafe` raised
    (e.g. the loop was closed during a shutdown race).  In all failure paths
    the coroutine is :meth:`close`-d so it does not trigger
    ``"coroutine was never awaited"`` warnings or leak its frame.

    Callers retain full control over what to do with the returned future
    (call ``.result(timeout=...)``, attach ``add_done_callback``, ignore it
    fire-and-forget, etc.).
    Nz%s: loop is Nonez%s: %s)_DEFAULT_LOGGERasyncioiscoroutinecloselogrun_coroutine_threadsafe	Exception)r   r   r   r	   r
   r   excs          3/home/ice/.hermes/hermes-agent/agent/async_utils.pysafe_schedule_threadsafer   "   s    ( &&&OC|t$$ 	JJLLL	-{;;;t/d;;;   t$$ 	JJLLL	8[#666ttttt	s   A% %
B:/A B55B:task'asyncio.Future[Any]'Nonec                j    	 |                                   dS # t          j        t          f$ r Y dS w xY w)u  Retrieve a detached task's result without surfacing cancellation.

    Used as an ``add_done_callback`` on tasks that were cancelled and
    detached (e.g. an adapter close path that swallows ``CancelledError``
    past its teardown deadline). Observing ``task.exception()`` prevents
    "exception was never retrieved" noise on the event loop; cancellation
    and any terminal error are deliberately swallowed — the task's owner
    already gave up on it.
    N)	exceptionr   CancelledErrorr   )r   s    r   consume_detached_task_resultr%   G   sG    "I.   s    22)r   r   r   r   r   r   r	   r   r
   r   r   r   )r   r    r   r!   )__doc__
__future__r   r   loggingconcurrent.futuresr   typingr   r   r   	getLogger__name__r   DEBUGr   r%        r   <module>r0      s    , # " " " " "   % % % % % % + + + + + + + + + + $'#H-- (,=]" " " " " "J     r/   