# tractor: structured concurrent "actors".
# Copyright 2018-eternity Tyler Goodlet.

# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

'''
Infection apis for ``asyncio`` loops running ``trio`` using guest mode.

'''
from __future__ import annotations
import asyncio
from asyncio.exceptions import (
    CancelledError,
)
from contextlib import asynccontextmanager as acm
from dataclasses import dataclass
import inspect
import platform
import sys
import traceback
from typing import (
    Any,
    Callable,
    AsyncIterator,
    Awaitable,
)

import tractor
from tractor._exceptions import (
    InternalError,
    TrioTaskExited,
    TrioCancelled,
    AsyncioTaskExited,
    AsyncioCancelled,
)
from tractor.runtime._state import (
    debug_mode,
    _runtime_vars,
)
from tractor._context import Unresolved
from tractor import devx
from tractor.log import (
    get_logger,
    StackLevelAdapter,
)
# TODO, wite the equiv of `trio.abc.Channel` but without attrs..
# -[ ] `trionics.chan_types.ChanStruct` maybe?
# from tractor.msg import (
#     pretty_struct,
# )
from tractor.trionics import (
    is_multi_cancelled,
)
from tractor.trionics._broadcast import (
    broadcast_receiver,
    BroadcastReceiver,
)
import trio
from outcome import (
    Error,
    Outcome,
)

log: StackLevelAdapter = get_logger()


__all__ = [
    'run_task',
    'run_as_asyncio_guest',
]

if (_py_313 := (
        ('3', '13')
        ==
        platform.python_version_tuple()[:-1]
    )
):
    # 3.13+ only.. lel.
    # https://docs.python.org/3.13/library/asyncio-queue.html#asyncio.QueueShutDown
    from asyncio import (
        QueueShutDown,
    )
else:
    QueueShutDown = False


# TODO, generally speaking we can generalize this abstraction as,
#
# > A "SC linked, inter-event-loop" channel for comms between
# > a `parent: trio.Task` -> `child: asyncio.Task` pair.
#
# It is **very similar** in terms of its operation as a "supervision
# scope primitive" to that of our `._context.Context` with the only
# difference being in how the tasks conduct msg-passing comms.
#
# For `LinkedTaskChannel` we are passing the equivalent of (once you
# include all the recently added  `._trio/aio_to_raise`
# exd-as-signals) our SC-dialog-proto over each asyncIO framework's
# mem-chan impl,
#
# verus in `Context`
#
# We are doing the same thing but msg-passing comms happens over an
# IPC transport between tasks in different memory domains.

@dataclass
class LinkedTaskChannel(
    trio.abc.Channel,

    # XXX LAME! meta-base conflict..
    # pretty_struct.Struct,
):
    '''
    A "linked task channel" which allows for two-way synchronized msg
    passing between a ``trio``-in-guest-mode task and an ``asyncio``
    task scheduled in the host loop.

    '''
    # ?TODO, rename as `._aio_q` since it's 2-way?
    _to_aio: asyncio.Queue
    _from_aio: trio.MemoryReceiveChannel

    _to_trio: trio.MemorySendChannel
    _trio_cs: trio.CancelScope
    _trio_task: trio.Task
    _aio_task_complete: trio.Event

    _closed_by_aio_task: bool = False
    _suppress_graceful_exits: bool = True

    _trio_err: BaseException|None = None
    _trio_to_raise: (
        AsyncioTaskExited|  # aio task exits while trio ongoing
        AsyncioCancelled|  # aio task is (self-)cancelled
        BaseException|
        None
    ) = None
    _trio_exited: bool = False

    # set after `asyncio.create_task()`
    _aio_task: asyncio.Task|None = None
    _aio_err: BaseException|None = None
    _aio_to_raise: (
        TrioTaskExited|  # trio task exits while aio ongoing
        BaseException|
        None
    ) = None
    # _aio_first: Any|None = None  # TODO?
    _aio_result: Any|Unresolved = Unresolved

    def _final_result_is_set(self) -> bool:
        return self._aio_result is not Unresolved

    # TODO? equiv from `Context`?
    # @property
    # def has_outcome(self) -> bool:
    #     return (
    #         bool(self.maybe_error)
    #         or
    #         self._final_result_is_set()
    #     )

    async def wait_for_result(
        self,
        hide_tb: bool = True,

    ) -> Any:
        '''
        Wait for the `asyncio.Task.result()` from `trio`

        '''
        __tracebackhide__: bool = hide_tb
        assert self._portal, (
            '`Context.wait_for_result()` can not be called from callee side!'
        )
        if self._final_result_is_set():
            return self._aio_result

        async with translate_aio_errors(
            chan=self,
            wait_aio_task=False,
        ):
            await self._aio_task_complete.wait()

        if (
            not self._final_result_is_set()
        ):
            if (trio_to_raise := self._trio_to_raise):
                raise trio_to_raise from self._aio_err

            elif aio_err := self._aio_err:
                raise aio_err

            else:
                raise InternalError(
                    f'Asyncio-task has no result or error set !?\n'
                    f'{self._aio_task}'
                )

        return self._aio_result

    _broadcaster: BroadcastReceiver|None = None

    async def aclose(self) -> None:
        await self._from_aio.aclose()

    # ?TODO? async version of this?
    def started_nowait(
        self,
        val: Any = None,
    ) -> None:
        '''
        Synchronize aio-side with its trio-parent.

        '''
        self._aio_started_val = val
        return self._to_trio.send_nowait(val)

    # TODO, mk this side-agnostic?
    #
    # -[ ] add private meths for both sides and dynamically
    #    determine which to use based on task-type read at calltime?
    #    -[ ] `._recv_trio()`: receive to trio<-asyncio
    #    -[ ] `._send_trio()`: send from trio->asyncio
    #    -[ ] `._recv_aio()`: send from asyncio->trio
    #    -[ ] `._send_aio()`: receive to asyncio<-trio
    #
    # -[ ] pass the instance to the aio side instead of the separate
    #    per-side chan types?
    #
    async def receive(self) -> Any:
        '''
        Receive a value `trio.Task` <- `asyncio.Task`.

        Note the tasks in each loop are "SC linked" as a pair with
        exception/cancel handling to teardown both sides on any
        unexpected error or cancellation.

        '''
        try:
            # TODO: do we need this to guarantee asyncio code get's
            # cancelled in the case where the trio side somehow creates
            # a state where the asyncio cycle-task isn't getting the
            # cancel request sent by (in theory) the last checkpoint
            # cycle on the trio side?
            # await trio.lowlevel.checkpoint()
            return await self._from_aio.receive()

        except BaseException as err:
            async with translate_aio_errors(
                chan=self,
                # NOTE, determined by `open_channel_from()` input arg
                suppress_graceful_exits=self._suppress_graceful_exits,

                # XXX: obviously this will deadlock if an on-going stream is
                # being procesed.
                # wait_on_aio_task=False,
            ):
                raise err

    async def get(self) -> Any:
        '''
        Receive a value `asyncio.Task` <- `trio.Task`.

        This is equiv to `await self._to_aio.get()`.

        '''
        return await self._to_aio.get()

    async def send(self, item: Any) -> None:
        '''
        Send a value `trio.Task` -> `asyncio.Task`
        by enqueuing `item` onto the internal
        `asyncio.Queue` via `put_nowait()`.

        '''
        self._to_aio.put_nowait(item)

    # TODO? could we only compile-in this method on an instance
    # handed to the `asyncio`-side, i.e. the fn invoked with
    # `.open_channel_from()`.
    def send_nowait(
        self,
        item: Any,
    ) -> None:
        '''
        Send a value through FROM the `asyncio.Task` to
        the `trio.Task` NON-BLOCKING.

        This is equiv to `self._to_trio.send_nowait()`.

        '''
        self._to_trio.send_nowait(item)

    # TODO? needed?
    # async def wait_aio_complete(self) -> None:
    #     await self._aio_task_complete.wait()

    def cancel_asyncio_task(
        self,
        msg: str = '',
    ) -> None:
        self._aio_task.cancel(
            msg=msg,
        )

    def closed(self) -> bool:
        return self._from_aio._closed  # type: ignore

    # TODO: shoud we consider some kind of "decorator" system
    # that checks for structural-typing compatibliity and then
    # automatically adds this ctx-mngr-as-method machinery?
    @acm
    async def subscribe(
        self,

    ) -> AsyncIterator[BroadcastReceiver]:
        '''
        Allocate and return a ``BroadcastReceiver`` which delegates
        to this inter-task channel.

        This allows multiple local tasks to receive each their own copy
        of this message stream.

        See ``tractor._streaming.MsgStream.subscribe()`` for further
        similar details.
        '''
        if self._broadcaster is None:

            bcast = self._broadcaster = broadcast_receiver(
                self,
                # use memory channel size by default
                self._from_aio._state.max_buffer_size,  # type: ignore
                receive_afunc=self.receive,
            )

            self.receive = bcast.receive  # type: ignore

        async with self._broadcaster.subscribe() as bstream:
            assert bstream.key != self._broadcaster.key
            assert bstream._recv == self._broadcaster._recv
            yield bstream


def _run_asyncio_task(
    func: Callable,
    *,
    qsize: int = 1,
    provide_channels: bool = False,
    suppress_graceful_exits: bool = True,
    hide_tb: bool = True,
    **kwargs,

) -> LinkedTaskChannel:
    '''
    Run an `asyncio`-compat async function or generator in a task,
    return or stream the result back to the caller
    `trio.lowleve.Task`.

    '''
    __tracebackhide__: bool = hide_tb
    if not (actor := tractor.current_actor()).is_infected_aio():
        raise RuntimeError(
            f'`infect_asyncio: bool` mode is not enabled ??\n'
            f'Ensure you pass `ActorNursery.start_actor(infect_asyncio=True)`\n'
            f'\n'
            f'{actor}\n'
        )

    # ITC (inter task comms), these channel/queue names are mostly from
    # ``asyncio``'s perspective.
    aio_q = from_trio = asyncio.Queue(qsize)  # type: ignore
    to_trio, from_aio = trio.open_memory_channel(qsize)  # type: ignore

    args = tuple(inspect.getfullargspec(func).args)
    if getattr(func, '_tractor_steam_function', None):
        # the assumption is that the target async routine accepts the
        # send channel then it intends to yield more then one return
        # value otherwise it would just return ;P
        assert qsize > 1

    trio_task: trio.Task = trio.lowlevel.current_task()
    trio_cs = trio.CancelScope()
    aio_task_complete = trio.Event()

    chan = LinkedTaskChannel(
        _to_aio=aio_q,  # asyncio.Queue
        _from_aio=from_aio,  # recv chan
        _to_trio=to_trio,  # send chan
        _trio_cs=trio_cs,
        _trio_task=trio_task,
        _aio_task_complete=aio_task_complete,
        _suppress_graceful_exits=suppress_graceful_exits,
    )

    # allow target func to accept/stream results manually by name
    if 'to_trio' in args:
        kwargs['to_trio'] = to_trio

    if 'from_trio' in args:
        kwargs['from_trio'] = from_trio

    if 'chan' in args:
        kwargs['chan'] = chan

    if provide_channels:
        assert (
            'to_trio' in args
            or
            'chan' in args
        )

    coro = func(**kwargs)

    async def wait_on_coro_final_result(
        to_trio: trio.MemorySendChannel,
        coro: Awaitable,
        aio_task_complete: trio.Event,

    ) -> None:
        '''
        Await input `coro` as/in an `asyncio.Task` and deliver final
        `return`-ed result back to `trio`.

        '''
        nonlocal chan

        orig = result = id(coro)
        try:
            # XXX TODO UGH!
            # this seems to break a `test_sync_pause_from_aio_task`
            # in a REALLY weird way where a `dict` value for
            # `_runtime_vars['_root_addrs']` is delivered from the
            # parent actor??
            #
            # XXX => see masked `.set_trace()` block in
            # `Actor.from_parent()`..
            #
            # with devx.maybe_open_crash_handler(
            #     # XXX, if trio-side exits (intentionally) we
            #     # shouldn't care bc it should have its own crash
            #     # handling logic.
            #     ignore={TrioTaskExited,},
            # ) as _bxerr:
            result: Any = await coro

            chan._aio_result = result
        except BaseException as aio_err:
            chan._aio_err = aio_err
            if isinstance(aio_err, CancelledError):
                log.runtime(
                    '`asyncio` task was cancelled..\n'
                )
            else:
                log.exception(
                    '`asyncio` task errored\n'
                )
            raise
        else:
            if (
                result != orig
                and
                chan._aio_err is None
                and

                # in the `open_channel_from()` case we don't
                # relay through the "return value".
                not provide_channels
            ):
                to_trio.send_nowait(result)

        finally:
            # if the task was spawned using `open_channel_from()`
            # then we close the channels on exit.
            if provide_channels:
                # breakpoint()  # TODO! why no work!?
                # import pdbp; pdbp.set_trace()

                # IFF there is a blocked trio waiter, we set the
                # aio-side error to be an explicit "exited early"
                # (much like a `Return` in our SC IPC proto) for the
                # `.open_channel_from()` case where the parent trio
                # task might not wait directly for a final returned
                # result (i.e. the trio side might be waiting on
                # a streamed value) - this is a signal that the
                # asyncio.Task has returned early!
                #
                # TODO, solve other cases where trio side might,
                # - raise Cancelled but aio side exits on next tick.
                # - raise error but aio side exits on next tick.
                # - raise error and aio side errors "independently"
                #   on next tick (SEE draft HANDLER BELOW).
                stats: trio.MemoryChannelStatistics = to_trio.statistics()
                if (
                    stats.tasks_waiting_receive
                    and
                    not chan._aio_err
                ):
                    chan._trio_to_raise = AsyncioTaskExited(
                        f'Task exited with final result: {result!r}\n'
                    )

                # XXX ALWAYS close the child-`asyncio`-task-side's
                # `to_trio` handle which will in turn relay
                # a `trio.EndOfChannel` to the `trio`-parent.
                # Consequently the parent `trio` task MUST ALWAYS
                # check for any `chan._aio_err` to be raised when it
                # receives an EoC.
                #
                # NOTE, there are 2 EoC cases,
                # - normal/graceful EoC due to the aio-side actually
                #   terminating its "streaming", but the task did not
                #   error and is not yet complete.
                #
                # - the aio-task terminated and we specially mark the
                #   closure as due to the `asyncio.Task`'s exit.
                #
                to_trio.close()
                chan._closed_by_aio_task = True

            aio_task_complete.set()
            log.runtime(
                f'`asyncio` task completed\n'
                f')>\n'
                f' |_{task}\n'
            )

    # start the asyncio task we submitted from trio
    if not inspect.isawaitable(coro):
        raise TypeError(
            f'Pass the async-fn NOT a coroutine\n'
            f'{coro!r}'
        )

    # schedule the (bg) `asyncio.Task`
    task: asyncio.Task = asyncio.create_task(
        wait_on_coro_final_result(
            to_trio,
            coro,
            aio_task_complete
        )
    )
    chan._aio_task: asyncio.Task = task

    # XXX TODO XXX get this actually workin.. XD
    # -[ ] we need logic to setup `greenback` for `asyncio`-side task
    #     REPLing.. which should normally be nearly the same as for
    #     `trio`?
    # -[ ] add to a new `.devx._greenback.maybe_init_for_asyncio()`?
    if (
        debug_mode()
        and
        (greenback := devx.debug.maybe_import_greenback(
            force_reload=True,
            raise_not_found=False,
        ))
    ):
        log.devx(
            f'Bestowing `greenback` portal for `asyncio`-task\n'
            f'{task}\n'
        )
        greenback.bestow_portal(task)

    def signal_trio_when_done(
        task: asyncio.Task,
    ) -> None:
        '''
        Maybe-cancel, relay-and-raise an error to, OR pack a final
        `return`-value for the parent (in SC terms) `trio.Task` on
        completion of the `asyncio.Task`.

        Note for certain "edge" scheduling-race-conditions we allow
        the aio side to dictate dedicated `tractor`-defined excs to
        be raised in the `trio` parent task; the intention is to
        indicate those races in a VERY pedantic manner!

        '''
        nonlocal chan
        trio_err: BaseException|None = chan._trio_err

        # XXX, since the original error we read from the asyncio.Task
        # might change between BEFORE and AFTER we here call
        # `asyncio.Task.result()`
        #
        # -> THIS is DUE TO US in `translate_aio_errors()`!
        #
        # => for example we might set a special exc
        # (`AsyncioCancelled|AsyncioTaskExited`) meant to be raised
        # in trio (and maybe absorbed depending on the called API)
        # BEFORE this done-callback is invoked by `asyncio`'s
        # runtime.
        trio_to_raise: BaseException|None = chan._trio_to_raise
        orig_aio_err: BaseException|None = chan._aio_err
        aio_err: BaseException|None = None

        # only to avoid `asyncio` complaining about uncaptured
        # task exceptions
        try:
            res: Any = task.result()
            log.info(
                f'`trio` received final result from `asyncio` task,\n'
                f')> {res}\n'
                f' |_{task}\n'
            )
            if not chan._aio_result:
                chan._aio_result = res

            # ?TODO, should we also raise `AsyncioTaskExited[res]`
            # in any case where trio is NOT blocking on the
            # `._to_trio` chan?
            #
            # -> ?NO RIGHT? since the
            #   `open_channel_from().__aexit__()` should detect this
            #   and then set any final `res` from above as a field
            #   that can optionally be read by the trio-paren-task as
            #   needed (just like in our
            #   `Context.wait_for_result()/.result` API yah?
            #
            # if provide_channels:

        except BaseException as _aio_err:
            aio_err: BaseException = _aio_err

            # READ AGAIN, AFTER the `asyncio` side errors, in case
            # it was cancelled due to an error from `trio` (or
            # some other out of band exc) and then set to something
            # else?
            curr_aio_err: BaseException|None = chan._aio_err

            # always true right?
            assert (
                type(aio_err)
                is type(orig_aio_err)
                is type(curr_aio_err)
            ), (
                f'`asyncio`-side task errors mismatch?!?\n\n'
                f'(caught) aio_err: {aio_err}\n'
                f'ORIG chan._aio_err: {orig_aio_err}\n'
                f'chan._aio_err: {curr_aio_err}\n'
            )

            msg: str = (
                '`trio`-side reports that the `asyncio`-side '
                '{etype_str}\n'
                # ^NOTE filled in below
            )
            if isinstance(aio_err, CancelledError):
                msg += (
                    f'c)>\n'
                    f' |_{task}\n'
                )
                log.cancel(
                    msg.format(etype_str='cancelled')
                )

            # XXX when the asyncio.Task exits early (before the trio
            # side) we relay through an exc-as-signal which is
            # normally suppressed unless the trio.Task also errors
            #
            # ?TODO, is this even needed (does it happen) now?
            elif (
                _py_313
                and
                isinstance(aio_err, QueueShutDown)
            ):
                # import pdbp; pdbp.set_trace()
                trio_err = AsyncioTaskExited(
                    'Task exited before `trio` side'
                )
                if not chan._trio_err:
                    chan._trio_err = trio_err

                msg += (
                    f')>\n'
                    f' |_{task}\n'
                )
                log.info(
                    msg.format(etype_str='exited')
                )

            else:
                msg += (
                    f'x)>\n'
                    f' |_{task}\n'
                )
                log.exception(
                    msg.format(etype_str='errored')
                )

        # is trio the src of the aio task's exc-as-outcome?
        trio_err: BaseException|None = chan._trio_err
        curr_aio_err: BaseException|None = chan._aio_err
        if (
            curr_aio_err
            or
            trio_err
            or
            trio_to_raise
        ):
            # XXX, if not already, ALWAYs cancel the trio-side on an
            # aio-side error or early return. In the case where the trio task is
            # blocking on a checkpoint or `asyncio.Queue.get()`.

            # NOTE: currently mem chan closure may act as a form
            # of error relay (at least in the `asyncio.CancelledError`
            # case) since we have no way to directly trigger a `trio`
            # task error without creating a nursery to throw one.
            # We might want to change this in the future though.
            from_aio.close()

            if (
                not trio_cs.cancelled_caught
                or
                not trio_cs.cancel_called
            ):
                log.cancel(
                    f'Cancelling trio-side due to aio-side src exc\n'
                    f'\n'
                    f'{curr_aio_err!r}\n'
                    f'\n'
                    f'(c>\n'
                    f'  |_{trio_task}\n'
                )
                trio_cs.cancel()

            # maybe the `trio` task errored independent from the
            # `asyncio` one and likely in between
            # a guest-run-sched-tick.
            #
            # The obvious ex. is where one side errors during
            # the current tick and then the other side immediately
            # errors before its next checkpoint; i.e. the 2 errors
            # are "independent".
            #
            # "Independent" here means in the sense that neither task
            # was the explicit cause of the other side's exception
            # according to our `tractor.to_asyncio` SC API's error
            # relaying mechanism(s); the error pair is *possibly
            # due-to* but **not necessarily** inter-related by some
            # (subsys) state between the tasks,
            #
            # NOTE, also see the `test_trio_prestarted_task_bubbles`
            # for reproducing detailed edge cases as per the above
            # cases.
            #
            trio_to_raise: AsyncioCancelled|AsyncioTaskExited = chan._trio_to_raise
            aio_to_raise: TrioTaskExited|TrioCancelled = chan._aio_to_raise
            if (
                not chan._aio_result
                and
                not trio_cs.cancelled_caught
                and (
                    (aio_err and type(aio_err) not in {
                        asyncio.CancelledError
                    })
                    or
                    aio_to_raise
                )
                and (
                    ((trio_err := chan._trio_err) and type(trio_err) not in {
                        trio.Cancelled,
                    })
                    or
                    trio_to_raise
                )
            ):
                eg = ExceptionGroup(
                    'Both the `trio` and `asyncio` tasks errored independently!!\n',
                    (
                        trio_to_raise or trio_err,
                        aio_to_raise or aio_err,
                    ),
                )
                # chan._trio_err = eg
                # chan._aio_err = eg
                raise eg

            elif aio_err:
                # XXX raise any `asyncio` side error IFF it doesn't
                # match the one we just caught from the task above!
                # (that would indicate something weird/very-wrong
                # going on?)
                if (
                    aio_err is not trio_to_raise
                    and (
                        not suppress_graceful_exits
                        and (
                            chan._aio_result is not Unresolved
                            and
                            isinstance(trio_to_raise, AsyncioTaskExited)
                        )
                    )
                ):
                    # raise aio_err from relayed_aio_err
                    raise trio_to_raise from curr_aio_err

                raise aio_err

    task.add_done_callback(signal_trio_when_done)
    return chan


def maybe_signal_aio_task(
    aio_task: asyncio.Task,
    exc: BaseException,
    *,
    cause: BaseException|None = None,
    pre_captured_fut: asyncio.Future|None = None,
    allow_cancel_fallback: bool = False,

) -> tuple[bool, str]:
    '''
    Best-effort delivery of `exc` to a still-running `aio_task`
    via its `_fut_waiter` (the `asyncio.Future` the task is
    currently `await`-ing on).

    Returns `(delivered, report)` where `delivered=True` iff
    either,
      - `fut.set_exception(exc)` was successfully called on an
        un-`done()` `_fut_waiter`, OR
      - the cancel-fallback path fired (only when the caller
        opted-in via `allow_cancel_fallback=True`).

    Why `_fut_waiter.set_exception(exc)` and NOT
    `aio_task.set_exception(exc)`:

      On py3.13+ `asyncio.Task.set_exception()` ALWAYS raises
      `RuntimeError("Task does not support set_exception
      operation")` — so calling it as a relay mechanism is dead
      code. The `_fut_waiter` is a plain `asyncio.Future` and
      its `set_exception()` works on all Python versions; the
      task's `_wakeup` callback then propagates the exc into
      the coro on its next tick.

    Why we PREFER NOT to call `aio_task.cancel()`:

      `Task.cancel()` injects a `CancelledError` that races
      any in-flight exception already queued on `_fut_waiter`
      (e.g. via a prior `set_exception()` from a sibling
      teardown path). The race can mask BOTH the original
      trio-side error and any asyncio-side error the task was
      mid-raising. See the
      `test_trio_closes_early_and_channel_exits` hang TODO
      around the `translate_aio_errors` finally for the
      historical artifact.

      However a caller may have NO OTHER way to terminate the
      task — when `_fut_waiter is None` AND the task is busy
      looping / runnable, neither `set_exception` nor a chan
      close can poke it. In that narrow case `cancel()` is the
      only available termination signal; opt-in via
      `allow_cancel_fallback=True`. The fallback NEVER runs
      when `_fut_waiter` carries an in-flight exc (the
      `fut.done()` branch); only when there's truly no
      `_fut_waiter` ref to poke.

    Pre-checkpoint capture:

      `asyncio.Task._wakeup` clears `_fut_waiter = None` as
      part of the wakeup sequence. If the caller crosses a
      trio checkpoint between fut-capture and this call,
      re-reading `aio_task._fut_waiter` will see `None` even
      though the exc is still in flight on the (now-`done()`)
      original fut. Pass `pre_captured_fut` to use the
      already-captured reference.

    Causal chaining via `cause`:

      Pass the underlying trio-side exc (the *reason* we're
      poking the aio side) via `cause` and the helper sets
      `exc.__cause__ = cause`. The chain travels with `exc`
      through `_fut_waiter.set_exception()` → `Task._wakeup`
      → coro raise → `wait_on_coro_final_result`'s except →
      `signal_trio_when_done`'s `task.result()`-`raise
      aio_err`. The final traceback then renders as
      "<trio-side exc> -> (direct cause of) -> <relay exc>"
      instead of an opaque, root-cause-detached relay.

    See the "cross-loop cause-chain matrix" comment in
    `translate_aio_errors()`'s final-raise block for how this
    `cause` interacts with every `raise X [from Y]` exit path
    (esp. the relay-echo guard which prevents a cause CYCLE).

    '''
    if cause is not None and exc.__cause__ is None:
        exc.__cause__ = cause

    if aio_task.done():
        return False, (
            f'aio-task already done; nothing to signal\n'
            f' |_{aio_task!r}\n'
        )

    fut: asyncio.Future|None = (
        pre_captured_fut
        if pre_captured_fut is not None
        else aio_task._fut_waiter
    )

    if fut and not fut.done():
        fut.set_exception(exc)
        return True, (
            f'signalled aio-task via `_fut_waiter.set_exception()`\n'
            f'exc: {exc!r}\n'
            f' |_{aio_task!r}\n'
        )

    if fut and fut.done():
        # NEVER cancel here even when `allow_cancel_fallback=True`
        # — the in-flight exc on `fut` will terminate the task
        # on its next tick; injecting `CancelledError` on top
        # would race and mask the real exc.
        return False, (
            f'`_fut_waiter` already signalled with,\n'
            f' |_{fut.exception()!r}\n'
            f'aio-task will exit on next tick via the in-flight exc;\n'
            f'SKIPPING re-signal (would race in-flight delivery).\n'
            f' |_{aio_task!r}\n'
        )

    # fut is None — task is runnable (sitting in asyncio's
    # ready queue), not parked on a future we can poke.
    if allow_cancel_fallback:
        cancel_msg: str = (
            f'\n'
            f'MANUALLY Cancelling `asyncio`-task: '
            f'{aio_task.get_name()}!\n\n'
            f'**THIS CAN SILENTLY SUPPRESS ERRORS FYI\n\n'
        )
        aio_task.cancel(msg=cancel_msg)
        return True, (
            f'aio-task has no `_fut_waiter`; FALLBACK cancel issued\n'
            f'(caller opted-in via `allow_cancel_fallback=True`).\n'
            f'{cancel_msg}'
            f' |_{aio_task!r}\n'
        )

    return False, (
        f'aio-task has no `_fut_waiter`; cannot signal without\n'
        f'`aio_task.cancel()` which can mask errors.\n'
        f'LEAVING AS-IS (caller did NOT opt-in to cancel fallback);\n'
        f'task should exit via chan close / aio-loop teardown\n'
        f'already in flight.\n'
        f' |_{aio_task!r}\n'
    )


@acm
async def translate_aio_errors(
    chan: LinkedTaskChannel,
    wait_on_aio_task: bool = False,
    cancel_aio_task_on_trio_exit: bool = True,
    suppress_graceful_exits: bool = True,

    hide_tb: bool = True,

) -> AsyncIterator[None]:
    '''
    An error handling to cross-loop propagation context around
    `asyncio.Task` spawns via one of this module's APIs:

    - `open_channel_from()`
    - `run_task()`

    appropriately translates errors and cancels into ``trio`` land.

    '''
    __tracebackhide__: bool = hide_tb

    trio_task = trio.lowlevel.current_task()
    aio_err: BaseException|None = chan._aio_err
    aio_task: asyncio.Task = chan._aio_task
    aio_done_before_trio: bool = aio_task.done()
    assert aio_task
    trio_err: BaseException|None = None
    eoc: trio.EndOfChannel|None = None
    try:
        yield  # back to one of the cross-loop apis
    except trio.Cancelled as taskc:
        trio_err = taskc
        chan._trio_err = trio_err

        # should NEVER be the case that `trio` is cancel-handling
        # BEFORE the other side's task-ref was set!?
        assert chan._aio_task

        # import pdbp; pdbp.set_trace()  # lolevel-debug

        # relay cancel through to called `asyncio` task
        chan._aio_to_raise = TrioCancelled(
            f'trio`-side cancelled the `asyncio`-side,\n'
            f'c)>\n'
            f'  |_{trio_task}\n'
            f'\n'
            f'trio src exc: {trio_err!r}\n'
        )

        # XXX NOTE XXX seems like we can get all sorts of unreliable
        # behaviour from `asyncio` under various cancellation
        # conditions (like SIGINT/kbi) when this is used..
        # SO FOR NOW, try to avoid it at most costs!
        #
        # aio_task.cancel(
        #     msg=f'the `trio` parent task was cancelled: {trio_task.name}'
        # )
        # raise

    # XXX EoC is a special SIGNAL from the aio-side here!
    # There are 2 cases to handle:
    # 1. the "EoC passthrough" case.
    #   - the aio-task actually closed the channel "gracefully" and
    #     the trio-task should unwind any ongoing channel
    #     iteration/receiving,
    #  |_this exc-translator wraps calls to `LinkedTaskChannel.receive()`
    #    in which case we want to relay the actual "end-of-chan" for
    #    iteration purposes.
    #
    # 2. relaying the "asyncio.Task termination" case.
    #   - if the aio-task terminates, maybe with an error, AND the
    #    `open_channel_from()` API was used, it will always signal
    #    that termination.
    #  |_`wait_on_coro_final_result()` always calls
    #    `to_trio.close()` when `provide_channels=True` so we need to
    #    always check if there is an aio-side exc which needs to be
    #    relayed to the parent trio side!
    #  |_in this case the special `chan._closed_by_aio_task` is
    #    ALWAYS set.
    #
    except trio.EndOfChannel as _eoc:
        eoc = _eoc
        if (
            chan._closed_by_aio_task
            and
            aio_err
        ):
            log.cancel(
                f'The asyncio-child task terminated due to error\n'
                f'{aio_err!r}\n'
            )
            chan._trio_to_raise = aio_err
            trio_err = chan._trio_err = eoc
            #
            # ?TODO?, raise something like a,
            # chan._trio_to_raise = AsyncioErrored()
            # BUT, with the tb rewritten to reflect the underlying
            # call stack?
        else:
            trio_err = chan._trio_err = eoc

        raise eoc

    # NOTE ALSO SEE the matching note in the `cancel_trio()` asyncio
    # task-done-callback.
    #
    # when the aio side is (possibly self-)cancelled it will close
    # the `chan._to_trio` and thus trigger the trio side to raise
    # a dedicated `AsyncioCancelled`
    except (
        trio.ClosedResourceError,
    ) as cre:
        chan._trio_err = cre
        aio_err = chan._aio_err

        # XXX if an underlying `asyncio.CancelledError` triggered
        # this channel close, raise our (non-`BaseException`) wrapper
        # exception (`AsyncioCancelled`) from that source error.
        if (
            # aio-side is cancelled?
            # |_ first not set until it terminates??
            aio_task.cancelled()
            and
            type(aio_err) is CancelledError

            # TODO, if we want suppression of the
            # silent-exit-by-`trio` case?
            # -[ ] the parent task can also just catch it though?
            # -[ ] OR, offer a `signal_aio_side_on_exit=True` ??
        ):
            # await tractor.pause(shield=True)
            chan._trio_to_raise = AsyncioCancelled(
                f'asyncio`-side cancelled the `trio`-side,\n'
                f'c(>\n'
                f'  |_{aio_task}\n\n'

                f'(triggered on the `trio`-side by a {cre!r})\n'
            )
            # TODO?? needed or does this just get reraised in the
            # `finally:` block below?
            # raise to_raise_trio from aio_err

        # maybe the chan-closure is due to something else?
        else:
            raise cre

    except BaseException as _trio_err:
        trio_err = chan._trio_err = _trio_err
        # await tractor.pause(shield=True)  # workx!

        # !TODO! we need an inter-loop lock here to avoid aio-tasks
        # clobbering trio ones when both crash in debug-mode!
        #
        entered: bool = await devx.debug._maybe_enter_pm(
            trio_err,
            api_frame=inspect.currentframe(),
        )
        if (
            not entered
            and
            not is_multi_cancelled(trio_err)
        ):
            log.exception(
                '`trio`-side task errored?'
            )
            # __tracebackhide__: bool = False

        # TODO, just a log msg here indicating the scope closed
        # and that the trio-side expects that and what the final
        # result from the aio side was?
        #
        # if isinstance(chan._aio_err, AsyncioTaskExited):
        #     await tractor.pause(shield=True)

        # if aio side is still active relay the trio-side error
        # to it via `_fut_waiter.set_exception()`.
        # ?TODO, mk `AsyncioCancelled[typeof(trio_err)]` embed the
        # current exc?
        aio_taskc = TrioCancelled(
            f'The `trio`-side task crashed!\n'
            f'{trio_err}'
        )
        delivered, report = maybe_signal_aio_task(
            aio_task,
            aio_taskc,
            # so the relay carries a "<trio_err> -> caused ->
            # TrioCancelled" chain when it eventually re-raises
            # on the aio side.
            cause=trio_err,
        )
        if not delivered:
            wait_on_aio_task = False
        log.cancel(report)

    finally:
        # record wtv `trio`-side error transpired
        if trio_err:
            assert chan._trio_err is trio_err
            # if chan._trio_err is not trio_err:
            #     await tractor.pause(shield=True)

        ya_trio_exited: bool = chan._trio_exited
        graceful_trio_exit: bool = (
            ya_trio_exited
            and
            not chan._trio_err   # XXX CRITICAL, `asyncio.Task.cancel()` is cucked man..
        )

        # XXX NOTE! XXX by default always cancel the `asyncio` task if
        # we've made it this far and it's not done.
        # TODO, how to detect if there's an out-of-band error that
        # caused the exit?
        if (
            not aio_task.done()
            and (
                cancel_aio_task_on_trio_exit
                # and
                # chan._aio_err  # TODO, if it's not .done() is this possible?

            # did the `.open_channel_from()` parent caller already
            # (gracefully) exit scope before this translator was
            # invoked?
            # => since we couple the lifetime of the `asyncio.Task`
            #   to the `trio` parent task, it should should also be
            #   terminated via either,
            #
            #   1. raising an explicit `TrioTaskExited|TrioCancelled`
            #      in task via `asyncio.Task._fut_waiter.set_exception()`
            #
            #   2. or (worst case) by cancelling the aio task using
            #      the std-but-never-working `asyncio.Task.cancel()`
            #      (which i can't figure out why that nor
            #      `Task.set_exception()` seem to never ever do the
            #      rignt thing! XD).
                or
                graceful_trio_exit
            )
        ):
            report: str = (
                'trio-side exited silently!'
            )
            assert not chan._aio_err, (
                'WTF why duz asyncio have err but not dun?!'
            )

            # if the `trio.Task` terminated without raising
            # `trio.Cancelled` (curently handled above) there's
            # 2 posibilities,
            #
            # i. it raised a `trio_err`
            # ii. it did a "silent exit" where the
            #    `open_channel_from().__aexit__()` phase ran without
            #    any raise or taskc (task cancel) and no final result
            #    was collected (yet) from the aio side.
            #
            # SO, ensure the asyncio-side is notified and terminated
            # by a dedicated exc-as-signal which distinguishes
            # various aio-task-state at termination cases.
            #
            # Consequently if the aio task doesn't absorb said
            # exc-as-signal, the trio side should then see the same exc
            # propagate up through the .open_channel_from() call to
            # the parent task.
            #
            # if the `trio.Task` already exited (only can happen for
            # the `open_channel_from()` use case) block due to to
            # either plain ol' graceful `__aexit__()` or due to taskc
            # or an error, we ensure the aio-side gets signalled via
            # an explicit exception and its `Queue` is shutdown.
            if ya_trio_exited:
                # XXX py3.13+ ONLY..
                # raise `QueueShutDown` on next `Queue.get/put()`
                if _py_313:
                    chan._to_aio.shutdown()

                # XXX CRITICAL ordering: capture `_fut_waiter`
                # BEFORE the checkpoint. `asyncio.Task._wakeup`
                # clears `_fut_waiter = None` as part of wakeup,
                # so re-reading after the checkpoint loses the
                # ref even though the exc is still in-flight on
                # the (now-`done()`) original fut. The helper
                # uses `pre_captured_fut` to recover that.
                pre_cp_fut: asyncio.Future|None = aio_task._fut_waiter

                # pump this event-loop (well `Runner` but ya)
                # so the aio side can error on next tick and we
                # sync task states from here onward.
                await trio.lowlevel.checkpoint()

                if graceful_trio_exit:
                    relay_exc = TrioTaskExited(
                        f'the `trio.Task` gracefully exited but '
                        f'its `asyncio` peer is not done?\n'
                        f')>\n'
                        f' |_{trio_task}\n'
                        f'\n'
                        f'>>\n'
                        f' |_{aio_task!r}\n'
                    )
                else:
                    relay_exc = TrioTaskExited(
                        f'The `trio`-side task crashed!\n'
                        f'{trio_err}'
                    )

                delivered, signal_report = maybe_signal_aio_task(
                    aio_task,
                    relay_exc,
                    pre_captured_fut=pre_cp_fut,
                    # XXX historically this branch called
                    # `aio_task.cancel()` when `_fut_waiter`
                    # was None — required to actually terminate
                    # aio tasks that aren't parked on a poke-able
                    # future (e.g. the `aio_echo_server` loop in
                    # `test_echoserver_detailed_mechanics`). Opt
                    # into the fallback so we don't regress.
                    allow_cancel_fallback=True,
                    # carry the trio-side exc (if any) as the
                    # cause so the aio-side relay shows the
                    # real root-cause chain when re-raised.
                    cause=trio_err,
                )
                report += signal_report

            log.warning(report)

        # sync with the `asyncio.Task`'s completion to ensure any
        # error is captured and relayed (via
        # `channel._aio_err/._trio_to_raise`) BEFORE calling
        # `maybe_raise_aio_side_err()` below!
        #
        # NOTE, `wait_on_aio_task` may have been flipped to `False`
        # by `maybe_signal_aio_task()` above when delivery
        # failed (e.g. `_fut_waiter is None`) — in that case we
        # skip the wait since the aio task won't process our
        # relay exc and `_aio_task_complete` may never set.
        #
        if wait_on_aio_task:
            await chan._aio_task_complete.wait()
            log.debug(
                'asyncio-task is done and unblocked trio-side!\n'
            )

        # NOTE, was a `maybe_raise_aio_side_err()` closure that
        # i moved inline BP
        '''
        Raise any `trio`-side-caused cancellation or legit task
        error normally propagated from the caller of either,
          - `open_channel_from()`
          - `run_task()`

        '''
        # ===== cross-loop cause-chain matrix =====
        # How `(trio_err, aio_err, trio_to_raise)` resolve into ONE
        # terminal `raise X [from Y]` (or an early `return`).
        #
        # legend (the possible `X` / `Y` operands):
        # - trio_err        : `chan._trio_err`, the trio-side exc.
        # - aio_err         : `chan._aio_err`, the aio-side exc.
        # - trio_to_raise   : `chan._trio_to_raise`, a tractor-chosen
        #                     relay exc (`AsyncioCancelled`/`AsyncioTaskExited`).
        # - raise_from      : `trio_err if (aio_err is trio_to_raise)
        #                      else aio_err` (the chosen `__cause__`).
        # - relay-echo      : an `aio_err` that is one of OUR OWN
        #                     `TrioTaskExited|TrioCancelled` signals,
        #                     synth'd + delivered to the aio-side by
        #                     `maybe_signal_aio_task()`; its `__cause__`
        #                     is ALREADY `trio_err`.
        # - "(bare)"        : raised with NO explicit `from` clause.
        #
        # this block (final-raise in `translate_aio_errors`):
        #   condition                            =>  raises         from
        #   -----------------------------------      -------------  -----------
        #   not suppress_graceful_exits          =>  trio_to_raise  raise_from
        #   AsyncioTaskExited + trio Cancelled/None  => return (aio-exit ignored)
        #   AsyncioTaskExited + trio EoC         =>  trio_err       (bare)
        #   AsyncioCancelled  + trio Cancelled   =>  return (co-cancel ignored)
        #   trio_to_raise match catch-all        =>  trio_to_raise  raise_from
        #   aio_err is relay-echo  ◄── the GUARD  =>  trio_err       (bare)
        #   aio_err independent (real aio fail)  =>  trio_err       aio_err
        #   aio_err independent, no trio_err     =>  aio_err        (bare)
        #   only trio_err                        =>  trio_err       (bare)
        #
        # sibling block (`signal_trio_when_done()`, the aio done-cb):
        #   AsyncioTaskExited relay-out          =>  trio_to_raise  aio_err
        #   plain aio_err re-raise               =>  aio_err  (__cause__ preset)
        #
        # INVARIANT: a relay-echo must NEVER become `trio_err.__cause__`
        #  (it's ALREADY caused-BY `trio_err`) → doing so would CYCLE
        #  (`trio_err ◄─► relay`). So the guard raises the root
        #  `trio_err` bare; the relay still keeps its own correct
        #  "relay ◄ trio_err" chain for any aio-side inspection.
        # ===== / cross-loop cause-chain matrix =====
        aio_err: BaseException|None = chan._aio_err
        trio_to_raise: (
            AsyncioCancelled|
            AsyncioTaskExited|
            Exception|  # relayed from aio-task
            None
        ) = chan._trio_to_raise

        raise_from: Exception = (
            trio_err if (aio_err is trio_to_raise)
            else aio_err
        )

        if not suppress_graceful_exits:
            raise trio_to_raise from raise_from

        if trio_to_raise:
            match (
                trio_to_raise,
                trio_err,
            ):
                case (
                    AsyncioTaskExited(),
                    trio.Cancelled()|
                    None,
                ):
                    log.info(
                        'Ignoring aio exit signal since trio also exited!'
                    )
                    return

                case (
                    AsyncioTaskExited(),
                    trio.EndOfChannel(),
                ):
                    raise trio_err

                case (
                    AsyncioCancelled(),
                    trio.Cancelled(),
                ):
                    if not aio_done_before_trio:
                        log.info(
                            'Ignoring aio cancelled signal since trio was also cancelled!'
                        )
                        return
                case _:
                    raise trio_to_raise from raise_from

        # Check if the asyncio-side is the cause of the trio-side
        # error.
        elif (
            aio_err is not None
            and
            type(aio_err) is not AsyncioCancelled
        ):
            # XXX, if `aio_err` is one of OUR OWN relay-signals
            # (`TrioTaskExited`/`TrioCancelled`) that we delivered
            # to the aio-side via `maybe_signal_aio_task()`, AND
            # its `__cause__` already points back at `trio_err`,
            # then it's just a derivative ECHO of the trio-side
            # error, NOT an independent asyncio failure.
            #
            # Raising `trio_err from aio_err` here would invert
            # (and cyclically tangle) the cause chain since the
            # relay was itself caused-by `trio_err`:
            #
            #   trio_err.__cause__ = aio_err   (from `raise .. from`)
            #   aio_err.__cause__  = trio_err  (set in `maybe_signal_aio_task`)
            #
            # So raise the REAL root `trio_err` alone; the relay's
            # own `__cause__` chain still correctly reads
            # "TrioTaskExited <- trio_err" for aio-side inspection.
            if (
                trio_err is not None
                and
                isinstance(aio_err, (TrioTaskExited, TrioCancelled))
                and
                aio_err.__cause__ is trio_err
            ):
                raise trio_err

            # always raise from any captured asyncio error
            if trio_err:
                raise trio_err from aio_err

            # XXX NOTE! above in the `trio.ClosedResourceError`
            # handler we specifically set the
            # `aio_err = AsyncioCancelled` such that it is raised
            # as that special exc here!
            raise aio_err

        if trio_err:
            raise trio_err

        # ^^TODO?? case where trio_err is not None and
        # aio_err is AsyncioTaskExited => raise eg!
        # -[x] maybe use a match bc this get's real
        #     complex fast XD
        #  => i did this above for silent exit cases ya?


async def run_task(
    func: Callable,
    *,

    qsize: int = 2**10,
    **kwargs,

) -> Any:
    '''
    Run an `asyncio`-compat async function or generator in a task,
    return or stream the result back to `trio`.

    '''
    # simple async func
    chan: LinkedTaskChannel = _run_asyncio_task(
        func,
        qsize=1,
        **kwargs,
    )
    with chan._from_aio:
        async with translate_aio_errors(
            chan,
            wait_on_aio_task=True,
            suppress_graceful_exits=chan._suppress_graceful_exits,
        ):
            # return single value that is the output from the
            # ``asyncio`` function-as-task. Expect the mem chan api
            # to do the job of handling cross-framework cancellations
            # / errors via closure and translation in the
            # `translate_aio_errors()` in the above ctx mngr.

            return await chan._from_aio.receive()
            # return await chan.receive()


@acm
async def open_channel_from(
    target: Callable[..., Any],
    suppress_graceful_exits: bool = True,
    **target_kwargs,

) -> AsyncIterator[
    tuple[LinkedTaskChannel, Any]
]:
    '''
    Start an `asyncio.Task` as `target()` and open an
    inter-loop (linked) channel for streaming between
    it and the current `trio.Task`.

    A pair `(chan: LinkedTaskChannel, Any)` is delivered
    to the caller where the 2nd element is the value
    provided by the `asyncio.Task`'s unblocking call
    to `chan.started_nowait()`.

    '''
    chan: LinkedTaskChannel = _run_asyncio_task(
        target,
        qsize=2**8,
        provide_channels=True,
        suppress_graceful_exits=suppress_graceful_exits,
        **target_kwargs,
    )
    # TODO, tuple form here?
    async with chan._from_aio:
        async with translate_aio_errors(
            chan,
            wait_on_aio_task=True,
            suppress_graceful_exits=suppress_graceful_exits,
        ):
            # sync to a "started()"-like first delivered value from the
            # ``asyncio`` task.
            try:
                with (cs := chan._trio_cs):
                    first = await chan.receive()

                    # deliver stream handle upward
                    yield chan, first
            except trio.Cancelled as taskc:
                if cs.cancel_called:
                    if isinstance(chan._trio_to_raise, AsyncioCancelled):
                        log.cancel(
                            f'trio-side was manually cancelled by aio side\n'
                            f'|_c>}}{cs!r}?\n'
                        )
                    # TODO, maybe a special `TrioCancelled`???

                raise taskc

            finally:
                chan._trio_exited = True

                # when the aio side is still ongoing but trio exits
                # early we signal with a special exc (kinda like
                # a `Return`-msg for IPC ctxs)
                aio_task: asyncio.Task = chan._aio_task
                if not aio_task.done():
                    # capture the in-flight trio-side exc (if any)
                    # so the relay's `__cause__` chain shows the
                    # real root cause when the aio task re-raises.
                    # `sys.exc_info()[1]` is non-`None` only when
                    # the `try` body raised (graceful exit -> None).
                    trio_exc: BaseException|None = sys.exc_info()[1]
                    _, report = maybe_signal_aio_task(
                        aio_task,
                        TrioTaskExited(
                            f'but the child `asyncio` task is still running?\n'
                            f'>>\n'
                            f' |_{aio_task!r}\n'
                        ),
                        cause=trio_exc,
                    )
                    log.cancel(report)
                else:
                    chan._to_trio.close()


class AsyncioRuntimeTranslationError(RuntimeError):
    '''
    We failed to correctly relay runtime semantics and/or maintain SC
    supervision rules cross-event-loop.

    '''


def run_trio_task_in_future(
    async_fn,
    *args,
) -> asyncio.Future:
    '''
    Run an async-func as a `trio` task from an `asyncio.Task` wrapped
    in a `asyncio.Future` which is returned to the caller.

    Another astounding feat by the great @oremanj !!

    Bo

    '''
    result_future = asyncio.Future()
    cancel_scope = trio.CancelScope()
    finished: bool = False

    # monkey-patch the future's `.cancel()` meth to
    # allow cancellation relay to `trio`-task.
    cancel_message: str|None = None
    orig_cancel = result_future.cancel

    def wrapped_cancel(
        msg: str|None = None,
    ):
        nonlocal cancel_message
        if finished:
            # We're being called back after the task completed
            if msg is not None:
                return orig_cancel(msg)
            elif cancel_message is not None:
                return orig_cancel(cancel_message)
            else:
                return orig_cancel()

        if result_future.done():
            return False

        # Forward cancellation to the Trio task, don't mark
        # future as cancelled until it completes
        cancel_message = msg
        cancel_scope.cancel()
        return True

    result_future.cancel = wrapped_cancel

    async def trio_task() -> None:
        nonlocal finished
        try:
            with cancel_scope:
                try:
                    # TODO: type this with new tech in 3.13
                    result: Any = await async_fn(*args)
                finally:
                    finished = True

            # Propagate result or cancellation to the Future
            if cancel_scope.cancelled_caught:
                result_future.cancel()

            elif not result_future.cancelled():
                result_future.set_result(result)

        except BaseException as exc:
            # the result future gets all the non-Cancelled
            # exceptions. Any Cancelled need to keep propagating
            # out of this stack frame in order to reach the cancel
            # scope for which they're intended.
            cancelled: BaseException|None
            rest: BaseException|None
            if isinstance(exc, BaseExceptionGroup):
                cancelled, rest = exc.split(trio.Cancelled)

            elif isinstance(exc, trio.Cancelled):
                cancelled, rest = exc, None

            else:
                cancelled, rest = None, exc

            if not result_future.cancelled():
                if rest:
                    result_future.set_exception(rest)
                else:
                    result_future.cancel()

            if cancelled:
                raise cancelled

    trio.lowlevel.spawn_system_task(
        trio_task,
        name=async_fn,
    )
    return result_future


def run_as_asyncio_guest(
    trio_main: Callable,
    # ^-NOTE-^ when spawned with `infected_aio=True` this func is
    # normally `Actor._async_main()` as is passed by some boostrap
    # entrypoint like `._entry._trio_main()`.

    _sigint_loop_pump_delay: float = 0,

) -> None:
    # ^-TODO-^ technically whatever `trio_main` returns.. we should
    # try to use func-typevar-params at leaast by 3.13!
    # -[ ] https://typing.readthedocs.io/en/latest/spec/callables.html#callback-protocols
    # -[ ] https://peps.python.org/pep-0646/#using-type-variable-tuples-in-functions
    # -[ ] https://typing.readthedocs.io/en/latest/spec/callables.html#unpack-for-keyword-arguments
    # -[ ] https://peps.python.org/pep-0718/
    '''
    Entry for an "infected ``asyncio`` actor".

    Entrypoint for a Python process which starts the ``asyncio`` event
    loop and runs ``trio`` in guest mode resulting in a system where
    ``trio`` tasks can control ``asyncio`` tasks whilst maintaining
    SC semantics.

    '''
    # Uh, oh.
    #
    # :o
    #
    # looks like your stdlib event loop has caught a case of "the trios" !
    #
    # :O
    #
    # Don't worry, we've heard you'll barely notice.
    #
    # :)
    #
    # You might hallucinate a few more propagating errors and feel
    # like your digestion has slowed, but if anything get's too bad
    # your parents will know about it.
    #
    # B)
    #
    async def aio_main(trio_main):
        '''
        Main `asyncio.Task` which calls
        `trio.lowlevel.start_guest_run()` to "infect" the `asyncio`
        event-loop by embedding the `trio` scheduler allowing us to
        boot the `tractor` runtime and connect back to our parent.

        '''
        loop = asyncio.get_running_loop()
        trio_done_fute = asyncio.Future()
        startup_msg: str = (
            'Starting `asyncio` guest-loop-run\n'
            '-> got running loop\n'
            '-> built a `trio`-done future\n'
        )

        # TODO: is this evern run or needed?
        # -[ ] pretty sure it never gets run for root-infected-aio
        #     since this main task is always the parent of any
        #     eventual `open_root_actor()` call?
        if debug_mode():
            log.error(
                'Attempting to enter non-required `greenback` init '
                'from `asyncio` task ???'
            )
            # XXX make it obvi we know this isn't supported yet!
            assert 0
            # await debug.maybe_init_greenback(
            #     force_reload=True,
            # )

        def trio_done_callback(main_outcome: Outcome):
            log.runtime(
                f'`trio` guest-run finishing with outcome\n'
                f'>) {main_outcome}\n'
                f'|_{trio_done_fute}\n'
            )

            # import pdbp; pdbp.set_trace()
            if isinstance(main_outcome, Error):
                error: BaseException = main_outcome.error

                # show an dedicated `asyncio`-side tb from the error
                tb_str: str = ''.join(traceback.format_exception(error))
                log.exception(
                    'Guest-run errored!?\n\n'
                    f'{main_outcome}\n'
                    f'{error}\n\n'
                    f'{tb_str}\n'
                )
                trio_done_fute.set_exception(error)

                # raise inline
                main_outcome.unwrap()

            else:
                trio_done_fute.set_result(main_outcome)

            log.info(
                f'`trio` guest-run finished with,\n'
                f')>\n'
                f'|_{trio_done_fute}\n'
            )

        startup_msg += (
            f'-> created {trio_done_callback!r}\n'
            f'-> scheduling `trio_main`: {trio_main!r}\n'
        )

        # start the infection: run trio on the asyncio loop in "guest mode"
        log.runtime(
            f'{startup_msg}\n\n'
            +
            'Infecting `asyncio`-process with a `trio` guest-run!\n'
        )

        # TODO, somehow bootstrap this!
        _runtime_vars['_is_infected_aio'] = True

        trio.lowlevel.start_guest_run(
            trio_main,
            run_sync_soon_threadsafe=loop.call_soon_threadsafe,
            done_callback=trio_done_callback,
        )
        fute_err: BaseException|None = None
        try:
            out: Outcome = await asyncio.shield(trio_done_fute)
            # out: Outcome = await trio_done_fute
            # ^TODO still don't really understand why the `.shield()`
            # is required ... ??
            # https://docs.python.org/3/library/asyncio-task.html#asyncio.shield
            # ^ seems as though in combo with the try/except here
            # we're BOLDLY INGORING cancel of the trio fute?
            #
            # I guess it makes sense bc we don't want `asyncio` to
            # cancel trio just because they can't handle SIGINT
            # sanely? XD .. kk

            # XXX, sin-shield causes guest-run abandons on SIGINT..
            # out: Outcome = await trio_done_fute

            # NOTE will raise (via `Error.unwrap()`) from any
            # exception packed into the guest-run's `main_outcome`.
            return out.unwrap()

        except (
            # XXX special SIGINT-handling is required since
            # `asyncio.shield()`-ing seems to NOT handle that case as
            # per recent changes in 3.11:
            # https://docs.python.org/3/library/asyncio-runner.html#handling-keyboard-interruption
            #
            # NOTE: further, apparently ONLY need to handle this
            # special SIGINT case since all other `asyncio`-side
            # errors can be processed via our `chan._aio_err`
            # relaying (right?); SIGINT seems to be totally diff
            # error path in `asyncio`'s runtime..?
            asyncio.CancelledError,

        ) as _fute_err:
            fute_err = _fute_err
            err_message: str = (
                'main `asyncio` task '
                'was cancelled!\n'
            )

            # TODO, handle possible edge cases with
            # `open_root_actor()` closing before this is run!
            #
            actor: tractor.Actor = tractor.current_actor()

            log.exception(
                err_message
                +
                'Cancelling `trio`-side `tractor`-runtime..\n'
                f'c(>\n'
                f'  |_{actor}.cancel_soon()\n'
            )

            # XXX WARNING XXX the next LOCs are super important!
            #
            # SINCE without them, we can get guest-run ABANDONMENT
            # cases where `asyncio` will not schedule or wait on the
            # guest-run `trio.Task` nor invoke its registered
            # `trio_done_callback()` before final shutdown!
            #
            # This is particularly true if the `trio` side has tasks
            # in shielded sections when an OC-cancel (SIGINT)
            # condition occurs!
            #
            # We now have the
            # `test_infected_asyncio.test_sigint_closes_lifetime_stack()`
            # suite to ensure we do not suffer this issues
            # (hopefully) ever again.
            #
            # The original abandonment issue surfaced as 2 different
            # race-condition dependent types scenarios all to do with
            # `asyncio` handling SIGINT from the system:
            #
            # - "silent-abandon" (WORST CASE):
            #  `asyncio` abandons the `trio` guest-run task silently
            #  and no `trio`-guest-run or `tractor`-actor-runtime
            #  teardown happens whatsoever..
            #
            # - "loud-abandon" (BEST-ish CASE):
            #   the guest run get's abaondoned "loudly" with `trio`
            #   reporting a console traceback and further tbs of all
            #   the (failed) GC-triggered shutdown routines which
            #   thankfully does get dumped to console..
            #
            # The abandonment is most easily reproduced if the `trio`
            # side has tasks doing shielded work where those tasks
            # ignore the normal `Cancelled` condition and continue to
            # run, but obviously `asyncio` isn't aware of this and at
            # some point bails on the guest-run unless we take manual
            # intervention..
            #
            # To repeat, *WITHOUT THIS* stuff below the guest-run can
            # get race-conditionally abandoned!!
            #
            # XXX SOLUTION XXX
            # ------ - ------
            # XXX FIRST PART:
            # ------ - ------
            # the obvious fix to the "silent-abandon" case is to
            # explicitly cancel the actor runtime such that no
            # runtime tasks are even left unaware that the guest-run
            # should be terminated due to OS cancellation.
            #
            actor.cancel_soon()

            # ------ - ------
            # XXX SECOND PART:
            # ------ - ------
            # Pump the `asyncio` event-loop to allow
            # `trio`-side to `trio`-guest-run to complete and
            # teardown !!
            #
            # oh `asyncio`, how i don't miss you at all XD
            while not trio_done_fute.done():
                log.runtime(
                    'Waiting on main guest-run `asyncio` task to complete..\n'
                    f'|_trio_done_fut: {trio_done_fute}\n'
                )
                await asyncio.sleep(_sigint_loop_pump_delay)

                # XXX is there any alt API/approach like the internal
                # call below but that doesn't block indefinitely..?
                # loop._run_once()

            try:
                return trio_done_fute.result()
            except (
                asyncio.InvalidStateError,
                # asyncio.CancelledError,
                # ^^XXX `.shield()` call above prevents this??

            )as state_err:

                # XXX be super dupere noisy about abandonment issues!
                aio_task: asyncio.Task = asyncio.current_task()
                message: str = (
                    'The `asyncio`-side task likely exited before the '
                    '`trio`-side guest-run completed!\n\n'
                )
                if fute_err:
                    message += (
                        f'The main {aio_task}\n'
                        f'STOPPED due to {type(fute_err)}\n\n'
                    )

                message += (
                    f'Likely something inside our guest-run-as-task impl is '
                    f'not effectively waiting on the `trio`-side to complete ?!\n'
                    f'This code -> {aio_main!r}\n\n'

                    'Below you will likely see a '
                    '"RuntimeWarning: Trio guest run got abandoned.." !!\n'
                )
                raise AsyncioRuntimeTranslationError(message) from state_err

        # XXX, should never get here ;)
        # else:
        #     import pdbp; pdbp.set_trace()

    # might as well if it's installed.
    try:
        import uvloop
        loop = uvloop.new_event_loop()
        asyncio.set_event_loop(loop)
    except ImportError:
        log.runtime('`uvloop` not available..')

    return asyncio.run(
        aio_main(trio_main),
    )
