diff --git a/newsfragments/882.feature.rst b/newsfragments/882.feature.rst
new file mode 100644
index 00000000..4c84c2f3
--- /dev/null
+++ b/newsfragments/882.feature.rst
@@ -0,0 +1,5 @@
+Trio now gives a reasonable traceback and error message in most cases
+when its invariants surrounding cancel scope nesting have been
+violated. (One common source of such violations is an async generator
+that yields within a cancel scope.) The previous behavior was an
+inscrutable chain of TrioInternalErrors.
diff --git a/trio/_core/_run.py b/trio/_core/_run.py
index 5b5e2921..9d163912 100644
--- a/trio/_core/_run.py
+++ b/trio/_core/_run.py
@@ -173,7 +173,8 @@ class CancelStatus:
     # The CancelStatus whose cancellations can propagate to us; we
     # become effectively cancelled when they do, unless scope.shield
     # is True.  May be None (for the outermost CancelStatus in a call
-    # to trio.run(), or briefly during TaskStatus.started()).
+    # to trio.run(), briefly during TaskStatus.started(), or during
+    # recovery from mis-nesting of cancel scopes).
     _parent = attr.ib(default=None, repr=False)
 
     # All of the CancelStatuses that have this CancelStatus as their parent.
@@ -185,6 +186,12 @@ class CancelStatus:
     # Invariant: all(task._cancel_status is self for task in self._tasks)
     _tasks = attr.ib(factory=set, init=False, repr=False)
 
+    # Set to True on still-active cancel statuses that are children
+    # of a cancel status that's been closed. This is used to permit
+    # recovery from mis-nested cancel scopes (well, at least enough
+    # recovery to show a useful traceback).
+    abandoned_by_misnesting = attr.ib(default=False, init=False, repr=False)
+
     def __attrs_post_init__(self):
         if self._parent is not None:
             self._parent._children.add(self)
@@ -213,9 +220,43 @@ class CancelStatus:
     def tasks(self):
         return frozenset(self._tasks)
 
+    def encloses(self, other):
+        """Returns true if this cancel status is a direct or indirect
+        parent of cancel status *other*, or if *other* is *self*.
+        """
+        while other is not None:
+            if other is self:
+                return True
+            other = other.parent
+        return False
+
     def close(self):
-        assert not self._tasks and not self._children
         self.parent = None  # now we're not a child of self.parent anymore
+        if self._tasks or self._children:
+            # Cancel scopes weren't exited in opposite order of being
+            # entered. CancelScope._close() deals with raising an error
+            # if appropriate; our job is to leave things in a reasonable
+            # state for unwinding our dangling children. We choose to leave
+            # this part of the CancelStatus tree unlinked from everyone
+            # else, cancelled, and marked so that exiting a CancelScope
+            # within the abandoned subtree doesn't affect the active
+            # CancelStatus. Note that it's possible for us to get here
+            # without CancelScope._close() raising an error, if a
+            # nursery's cancel scope is closed within the nursery's
+            # nested child and no other cancel scopes are involved,
+            # but in that case task_exited() will deal with raising
+            # the error.
+            self._mark_abandoned()
+
+            # Since our CancelScope is about to forget about us, and we
+            # have no parent anymore, there's nothing left to call
+            # recalculate(). So, we can stay cancelled by setting
+            # effectively_cancelled and updating our children.
+            self.effectively_cancelled = True
+            for task in self._tasks:
+                task._attempt_delivery_of_any_pending_cancel()
+            for child in self._children:
+                child.recalculate()
 
     @property
     def parent_cancellation_is_visible_to_us(self):
@@ -237,6 +278,11 @@ class CancelStatus:
             for child in self._children:
                 child.recalculate()
 
+    def _mark_abandoned(self):
+        self.abandoned_by_misnesting = True
+        for child in self._children:
+            child._mark_abandoned()
+
     def effective_deadline(self):
         if self.effectively_cancelled:
             return -inf
@@ -245,6 +291,30 @@ class CancelStatus:
         return min(self._scope.deadline, self._parent.effective_deadline())
 
 
+MISNESTING_ADVICE = """
+This is probably a bug in your code, that has caused Trio's internal state to
+become corrupted. We'll do our best to recover, but from now on there are
+no guarantees.
+
+Typically this is caused by one of the following:
+  - yielding within a generator or async generator that's opened a cancel
+    scope or nursery (unless the generator is a @contextmanager or
+    @asynccontextmanager); see https://github.com/python-trio/trio/issues/638
+  - manually calling __enter__ or __exit__ on a trio.CancelScope, or
+    __aenter__ or __aexit__ on the object returned by trio.open_nursery();
+    doing so correctly is difficult and you should use @[async]contextmanager
+    instead, or maybe [Async]ExitStack
+  - using [Async]ExitStack to interleave the entries/exits of cancel scopes
+    and/or nurseries in a way that couldn't be achieved by some nesting of
+    'with' and 'async with' blocks
+  - using the low-level coroutine object protocol to execute some parts of
+    an async function in a different cancel scope/nursery context than
+    other parts
+If you don't believe you're doing any of these things, please file a bug:
+https://github.com/python-trio/trio/issues/new
+"""
+
+
 @attr.s(cmp=False, repr=False, slots=True)
 class CancelScope(metaclass=Final):
     """A *cancellation scope*: the link between a unit of cancellable
@@ -319,13 +389,60 @@ class CancelScope(metaclass=Final):
         return exc
 
     def _close(self, exc):
+        if self._cancel_status is None:
+            new_exc = RuntimeError(
+                "Cancel scope stack corrupted: attempted to exit {!r} "
+                "which had already been exited".format(self)
+            )
+            new_exc.__context__ = exc
+            return new_exc
         scope_task = current_task()
+        if scope_task._cancel_status is not self._cancel_status:
+            # Cancel scope mis-nesting: this cancel scope isn't the most
+            # recently opened by this task (that's still open). That is,
+            # our assumptions about context managers forming a stack
+            # have been violated. Try and make the best of it.
+            if self._cancel_status.abandoned_by_misnesting:
+                # We are an inner cancel scope that was still active when
+                # some outer scope was closed. The closure of that outer
+                # scope threw an error, so we don't need to throw another
+                # one; it would just confuse the traceback.
+                pass
+            elif not self._cancel_status.encloses(scope_task._cancel_status):
+                # This task isn't even indirectly contained within the
+                # cancel scope it's trying to close. Raise an error
+                # without changing any state.
+                new_exc = RuntimeError(
+                    "Cancel scope stack corrupted: attempted to exit {!r} "
+                    "from unrelated {!r}\n{}".format(
+                        self, scope_task, MISNESTING_ADVICE
+                    )
+                )
+                new_exc.__context__ = exc
+                return new_exc
+            else:
+                # Otherwise, there's some inner cancel scope(s) that
+                # we're abandoning by closing this outer one.
+                # CancelStatus.close() will take care of the plumbing;
+                # we just need to make sure we don't let the error
+                # pass silently.
+                new_exc = RuntimeError(
+                    "Cancel scope stack corrupted: attempted to exit {!r} "
+                    "in {!r} that's still within its child {!r}\n{}".format(
+                        self, scope_task, scope_task._cancel_status._scope,
+                        MISNESTING_ADVICE
+                    )
+                )
+                new_exc.__context__ = exc
+                exc = new_exc
+                scope_task._activate_cancel_status(self._cancel_status.parent)
+        else:
+            scope_task._activate_cancel_status(self._cancel_status.parent)
         if (
             exc is not None
             and not self._cancel_status.parent_cancellation_is_visible_to_us
         ):
             exc = MultiError.filter(self._exc_filter, exc)
-        scope_task._activate_cancel_status(self._cancel_status.parent)
         self._cancel_status.close()
         with self._might_change_registered_deadline():
             self._cancel_status = None
@@ -1155,6 +1272,29 @@ class Runner:
         return task
 
     def task_exited(self, task, outcome):
+        if (
+            task._cancel_status is not None
+            and task._cancel_status.abandoned_by_misnesting
+            and task._cancel_status.parent is None
+        ):
+            # The cancel scope surrounding this task's nursery was closed
+            # before the task exited. Force the task to exit with an error,
+            # since the error might not have been caught elsewhere. See the
+            # comments in CancelStatus.close().
+            try:
+                # Raise this, rather than just constructing it, to get a
+                # traceback frame included
+                raise RuntimeError(
+                    "Cancel scope stack corrupted: cancel scope surrounding "
+                    "{!r} was closed before the task exited\n{}".format(
+                        task, MISNESTING_ADVICE
+                    )
+                )
+            except RuntimeError as new_exc:
+                if isinstance(outcome, Error):
+                    new_exc.__context__ = outcome.error
+                outcome = Error(new_exc)
+
         task._activate_cancel_status(None)
         self.tasks.remove(task)
         if task is self.main_task:

Failing to close a CancelScope produces super confusing errors
(update: this appears to have been due to failing to close the CancelScope; see: https://github.com/python-trio/trio/issues/882#issuecomment-488817573)

I'm not sure either way if this is ultimately a dupe of #552, so feel free to close if so. I'm also having a hell of a hard time figuring out how to reliably reproduce this, and it may actually be an issue in trio_asyncio?

## What's the issue?

During teardown, there's a KeyError in ``trio._core._run:CancelScope._remove_task``.

## Steps to reproduce

I haven't been able to repro this outside of one specific script, but I've tried a lot of different things. For the sake of brevity, I'll call that script "voidreader", since that's what I'm actually calling it and if you can surmise any secret sauce from that name then you deserve to know anyways. Background:

+ Voidreader is starting from a call to ``trio.run``
+ It runs a [separate logging system in a companion thread](https://github.com/Badg/bescheid/tree/feature/mvp), also running via ``trio.run``, but I'm pretty confident that isn't related, because I still see the error when I comment out the logging system. However, that library does some patching of the stdlib logging module, which... may or may not be relevant?
+ Voidreader starts some async context managers pretty much immediately. One of them runs a Quart server inside of a call to ``aio_as_trio``; there's other uses of it as well
+ Voidreader is also using asks for some stuff, but I don't think that's relevant either
+ Control-C interrupts stop "just working" inside voidreader. It takes me several attempts to get it to actually exit, and then I see the traceback included below

I've tried reproing with stuff like this:

```python
from contextlib import asynccontextmanager
import asyncio

from trio_asyncio import aio_as_trio
from trio_asyncio import trio_as_aio
import trio
import trio_asyncio


@asynccontextmanager
async def nested():
    print('fly, you fools!')
    yield
    await aio_as_trio(asyncio.sleep(7))


async def main():
    async with trio_asyncio.open_loop() as loop:
        async with nested():
            await trio.sleep(1)


if __name__ == '__main__':
    trio.run(main)
```

but to no avail -- **note that control+C still works here, raising a keyboardinterrupt.** My hunch is that this may have something to do with the Quart stuff that's running in an asyncio context.

## Traceback

```
Traceback (most recent call last):
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 1323, in run
    run_impl(runner, async_fn, args)
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 1471, in run_impl
    runner.task_exited(task, final_outcome)
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 943, in task_exited
    task._cancel_stack[-1]._remove_task(task)
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 203, in _remove_task
    self._tasks.remove(task)
KeyError: <Task '__main__.main' at 0x27f9eaf5b00>

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Python37\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "C:\Python37\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "d:\dropbox\projekte\foo\bar\baz\__main__.py", line 36, in <module>
    trio.run(main)
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 1329, in run
    ) from exc
trio.TrioInternalError: internal error in trio - please file a bug!
Exception ignored in: <function Nursery.__del__ at 0x0000027F9CEE0730>
Traceback (most recent call last):
  File "C:\Users\Nick\venv\foo\lib\site-packages\trio\_core\_run.py", line 530, in __del__
AssertionError:
PS C:\Users\Nick>
```

## Expected behavior

At the very least, a better traceback -- at one point I thought I was seeing that traceback during *startup*, when ultimately the problem was a recursion issue in my logging patch. While I was trying to repro the trio issue, I stumbled upon the problem in my code, fixed it, and suddenly things ran happily again -- until shutdown, that is, which is where the traceback was originating from anyways. Having more information about surrounding exceptions would have been really helpful early on, to help me isolate which problems were mine vs which problems might be upstream.

Of course also, I'd expect a cleaner shutdown! :)
diff --git a/trio/_core/tests/test_run.py b/trio/_core/tests/test_run.py
index f472dcf7..1369e39f 100644
--- a/trio/_core/tests/test_run.py
+++ b/trio/_core/tests/test_run.py
@@ -6,7 +6,7 @@ import threading
 import time
 import types
 import warnings
-from contextlib import contextmanager
+from contextlib import contextmanager, ExitStack
 from math import inf
 
 import attr
@@ -923,6 +923,76 @@ async def test_cancel_unbound():
     assert scope.cancel_called  # never become un-cancelled
 
 
+async def test_cancel_scope_misnesting():
+    outer = _core.CancelScope()
+    inner = _core.CancelScope()
+    with ExitStack() as stack:
+        stack.enter_context(outer)
+        with inner:
+            with pytest.raises(RuntimeError, match="still within its child"):
+                stack.close()
+        # No further error is raised when exiting the inner context
+
+    # If there are other tasks inside the abandoned part of the cancel tree,
+    # they get cancelled when the misnesting is detected
+    async def task1():
+        with pytest.raises(_core.Cancelled):
+            await sleep_forever()
+
+    # Even if inside another cancel scope
+    async def task2():
+        with _core.CancelScope():
+            with pytest.raises(_core.Cancelled):
+                await sleep_forever()
+
+    with ExitStack() as stack:
+        stack.enter_context(_core.CancelScope())
+        async with _core.open_nursery() as nursery:
+            nursery.start_soon(task1)
+            nursery.start_soon(task2)
+            await wait_all_tasks_blocked()
+            with pytest.raises(RuntimeError, match="still within its child"):
+                stack.close()
+
+    # Variant that makes the child tasks direct children of the scope
+    # that noticed the misnesting:
+    nursery_mgr = _core.open_nursery()
+    nursery = await nursery_mgr.__aenter__()
+    try:
+        nursery.start_soon(task1)
+        nursery.start_soon(task2)
+        nursery.start_soon(sleep_forever)
+        await wait_all_tasks_blocked()
+        nursery.cancel_scope.__exit__(None, None, None)
+    finally:
+        with pytest.raises(RuntimeError) as exc_info:
+            await nursery_mgr.__aexit__(*sys.exc_info())
+        assert "which had already been exited" in str(exc_info.value)
+        assert type(exc_info.value.__context__) is _core.MultiError
+        assert len(exc_info.value.__context__.exceptions) == 3
+        cancelled_in_context = False
+        for exc in exc_info.value.__context__.exceptions:
+            assert isinstance(exc, RuntimeError)
+            assert "closed before the task exited" in str(exc)
+            cancelled_in_context |= isinstance(
+                exc.__context__, _core.Cancelled
+            )
+        assert cancelled_in_context  # for the sleep_forever
+
+    # Trying to exit a cancel scope from an unrelated task raises an error
+    # without affecting any state
+    async def task3(task_status):
+        with _core.CancelScope() as scope:
+            task_status.started(scope)
+            await sleep_forever()
+
+    async with _core.open_nursery() as nursery:
+        scope = await nursery.start(task3)
+        with pytest.raises(RuntimeError, match="from unrelated"):
+            scope.__exit__(None, None, None)
+        scope.cancel()
+
+
 async def test_timekeeping():
     # probably a good idea to use a real clock for *one* test anyway...
     TARGET = 0.1

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest>=6.0.0 numpy>=1.16.0 pandas>=1.0.0
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
async-generator==1.10
attrs==25.3.0
exceptiongroup==1.2.2
idna==3.10
iniconfig==2.1.0
numpy==2.0.2
outcome==1.3.0.post0
packaging==24.2
pandas==2.2.3
pluggy==1.5.0
pytest==8.3.5
python-dateutil==2.9.0.post0
pytz==2025.2
six==1.17.0
sniffio==1.3.1
sortedcontainers==2.4.0
tomli==2.2.1
-e git+https://github.com/python-trio/trio.git@3305f7ff28564fc86e5a3db21ef3d41779552e98#egg=trio
tzdata==2025.2

name: trio
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - async-generator==1.10
      - attrs==25.3.0
      - exceptiongroup==1.2.2
      - idna==3.10
      - iniconfig==2.1.0
      - numpy==2.0.2
      - outcome==1.3.0.post0
      - packaging==24.2
      - pandas==2.2.3
      - pluggy==1.5.0
      - pytest==8.3.5
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - six==1.17.0
      - sniffio==1.3.1
      - sortedcontainers==2.4.0
      - tomli==2.2.1
      - trio==0.11.0+dev
      - tzdata==2025.2
prefix: /opt/conda/envs/trio

trio/_core/tests/test_run.py::test_cancel_scope_misnesting
trio/_core/tests/test_run.py::test_instruments_crash trio/_core/tests/test_run.py::test_broken_abort trio/_core/tests/test_run.py::test_error_in_run_loop
trio/_core/tests/test_run.py::test_basic trio/_core/tests/test_run.py::test_initial_task_error trio/_core/tests/test_run.py::test_run_nesting trio/_core/tests/test_run.py::test_nursery_warn_use_async_with trio/_core/tests/test_run.py::test_nursery_main_block_error_basic trio/_core/tests/test_run.py::test_child_crash_basic trio/_core/tests/test_run.py::test_basic_interleave trio/_core/tests/test_run.py::test_task_crash_propagation trio/_core/tests/test_run.py::test_main_and_task_both_crash trio/_core/tests/test_run.py::test_two_child_crashes trio/_core/tests/test_run.py::test_child_crash_wakes_parent trio/_core/tests/test_run.py::test_reschedule trio/_core/tests/test_run.py::test_current_time trio/_core/tests/test_run.py::test_current_time_with_mock_clock trio/_core/tests/test_run.py::test_current_clock trio/_core/tests/test_run.py::test_current_task trio/_core/tests/test_run.py::test_root_task trio/_core/tests/test_run.py::test_out_of_context trio/_core/tests/test_run.py::test_current_statistics trio/_core/tests/test_run.py::test_instruments trio/_core/tests/test_run.py::test_instruments_interleave trio/_core/tests/test_run.py::test_null_instrument trio/_core/tests/test_run.py::test_instrument_before_after_run trio/_core/tests/test_run.py::test_instrument_task_spawn_exit trio/_core/tests/test_run.py::test_cancel_scope_repr trio/_core/tests/test_run.py::test_cancel_points trio/_core/tests/test_run.py::test_cancel_edge_cases trio/_core/tests/test_run.py::test_cancel_scope_multierror_filtering trio/_core/tests/test_run.py::test_precancelled_task trio/_core/tests/test_run.py::test_cancel_shielding trio/_core/tests/test_run.py::test_cancel_inheritance trio/_core/tests/test_run.py::test_cancel_shield_abort trio/_core/tests/test_run.py::test_basic_timeout trio/_core/tests/test_run.py::test_cancel_scope_deprecated trio/_core/tests/test_run.py::test_cancel_scope_nesting trio/_core/tests/test_run.py::test_cancel_unbound trio/_core/tests/test_run.py::test_timekeeping trio/_core/tests/test_run.py::test_failed_abort trio/_core/tests/test_run.py::test_spawn_system_task trio/_core/tests/test_run.py::test_system_task_crash trio/_core/tests/test_run.py::test_system_task_crash_MultiError trio/_core/tests/test_run.py::test_system_task_crash_plus_Cancelled trio/_core/tests/test_run.py::test_system_task_crash_KeyboardInterrupt trio/_core/tests/test_run.py::test_yield_briefly_checks_for_timeout trio/_core/tests/test_run.py::test_exc_info trio/_core/tests/test_run.py::test_exc_info_after_yield_error trio/_core/tests/test_run.py::test_exception_chaining_after_yield_error trio/_core/tests/test_run.py::test_nursery_exception_chaining_doesnt_make_context_loops trio/_core/tests/test_run.py::test_TrioToken_identity trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_basic trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_too_late trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_idempotent trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_idempotent_requeue trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_after_main_crash trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_crashes trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_FIFO trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_starvation_resistance trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_threaded_stress_test trio/_core/tests/test_run.py::test_TrioToken_run_sync_soon_massive_queue trio/_core/tests/test_run.py::test_slow_abort_basic trio/_core/tests/test_run.py::test_slow_abort_edge_cases trio/_core/tests/test_run.py::test_task_tree_introspection trio/_core/tests/test_run.py::test_nursery_closure trio/_core/tests/test_run.py::test_spawn_name trio/_core/tests/test_run.py::test_current_effective_deadline trio/_core/tests/test_run.py::test_nice_error_on_bad_calls_to_run_or_spawn trio/_core/tests/test_run.py::test_calling_asyncio_function_gives_nice_error trio/_core/tests/test_run.py::test_trivial_yields trio/_core/tests/test_run.py::test_nursery_start trio/_core/tests/test_run.py::test_task_nursery_stack trio/_core/tests/test_run.py::test_nursery_start_with_cancelled_nursery trio/_core/tests/test_run.py::test_nursery_start_keeps_nursery_open trio/_core/tests/test_run.py::test_nursery_explicit_exception trio/_core/tests/test_run.py::test_nursery_stop_iteration trio/_core/tests/test_run.py::test_nursery_stop_async_iteration trio/_core/tests/test_run.py::test_traceback_frame_removal trio/_core/tests/test_run.py::test_contextvar_support trio/_core/tests/test_run.py::test_contextvar_multitask trio/_core/tests/test_run.py::test_system_task_contexts trio/_core/tests/test_run.py::test_Cancelled_init trio/_core/tests/test_run.py::test_Cancelled_str trio/_core/tests/test_run.py::test_Cancelled_subclass trio/_core/tests/test_run.py::test_CancelScope_subclass trio/_core/tests/test_run.py::test_sniffio_integration trio/_core/tests/test_run.py::test_Task_custom_sleep_data trio/_core/tests/test_run.py::test_permanently_detach_coroutine_object trio/_core/tests/test_run.py::test_detach_and_reattach_coroutine_object trio/_core/tests/test_run.py::test_detached_coroutine_cancellation
newsfragments/882.feature.rst trio/_core/_run.py
newsfragments/882.feature.rst trio/_core/_run.py