asyncio interop: ``tractor.to_asyncio``
=======================================

"Infected asyncio" mode: spawn an actor with
``start_actor(..., infect_asyncio=True)`` and its process runs
:mod:`trio` as a `guest`_ on top of the :mod:`asyncio` loop —
letting your trio task tree drive asyncio tasks *in the same
process* while the rest of the actor tree stays pure trio. Each
trio <-> asyncio task pair is linked with structured concurrency
(SC) semantics: error or cancellation on either side tears down
both, with the cause translated cross-loop.

.. d2:: diagrams/infected_aio.d2
   :caption: A trio guest driving asyncio tasks in one actor.
   :margin:
   :alt: trio guest mode inside an asyncio-infected actor

See ``examples/infected_asyncio_echo_server.py`` for a complete
worked example.

.. currentmodule:: tractor.to_asyncio

Starting asyncio tasks from trio
--------------------------------

.. autofunction:: open_channel_from

.. autofunction:: run_task

.. note::

   :func:`open_channel_from` mirrors the
   :meth:`tractor.Portal.open_context` handshake: the asyncio side calls
   ``chan.started_nowait(value)`` and that value pops out as
   ``first`` on the trio side. :func:`run_task` is the one-shot
   form — run a single asyncio-compatible coroutine fn and return
   its result to trio; :func:`tractor.to_actor.run` is its
   cross-process sibling.

The inter-loop channel
----------------------

.. autoclass:: LinkedTaskChannel
   :members: send,
             receive,
             wait_for_result,
             subscribe,
             started_nowait,
             send_nowait,
             get,
             cancel_asyncio_task,
             closed

.. note::

   The trio side uses the async API
   (:meth:`LinkedTaskChannel.send` /
   :meth:`LinkedTaskChannel.receive`); the asyncio side uses the
   loop-safe sync/await mix
   (:meth:`LinkedTaskChannel.send_nowait` /
   :meth:`LinkedTaskChannel.get` /
   :meth:`LinkedTaskChannel.started_nowait`).

Translated exception types
--------------------------

Cross-loop failures are re-raised on the *other* side as one of
these explicit translation types, so you always know which loop
actually died first:

.. autoexception:: tractor._exceptions.AsyncioCancelled

.. autoexception:: tractor._exceptions.AsyncioTaskExited

.. autoexception:: tractor._exceptions.TrioCancelled

.. autoexception:: tractor._exceptions.TrioTaskExited

.. autoexception:: AsyncioRuntimeTranslationError
   :show-inheritance:

Guest-mode entrypoint
---------------------

``run_as_asyncio_guest()`` is the runtime-internal entrypoint that
boots trio in `guest`_ mode inside an infected actor — you get it
implicitly via ``infect_asyncio=True`` and shouldn't need to call
it yourself.

.. seealso::

   :doc:`/api/core` for the ``infect_asyncio`` spawn flag,
   :meth:`tractor.Actor.is_infected_aio` for runtime
   introspection, :doc:`/api/devx` for using the debugger from
   inside asyncio tasks, and :doc:`/guide/asyncio` for the
   guided tour.

.. _guest: https://trio.readthedocs.io/en/stable/reference-lowlevel.html?highlight=guest%20mode#using-guest-mode-to-run-trio-on-top-of-other-event-loops
