Repository: python-trio/trio

Issue #882: Failing to close a CancelScope produces super confusing errors
Created: 2019-01-26T23:25:50Z
(update: this appears to have been due to failing to close the CancelScope; see: #882 (comment))
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, 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:
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! :)

Duplicate issue #1055: Poking at Trio with .send(None) and...  it told me to report this
Created: 2019-05-17T02:40:40Z
https://repl.it/@altendky/UnhappyGenuineConversions-4
I was just poking at Trio to see what happened and don't personally consider a failure here a bug.  But, the error told me to report it so here I am, being obedient.  If you close this issue saying that s.send(None) is evil and this is an ok way to respond to it, I'm totally fine with that.
import trio


async def main():
    s = trio.sleep(2)

    async with trio.open_nursery() as nursery:
        s.send(None)
        print('nursery end')

    print('main end')
 

trio.run(main)
Python 3.6.1 (default, Dec 2015, 13:05:11)
[GCC 4.8.2] on linux


Repl.it: Installing fresh packages

Collecting trio==0.11.0 (from -r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/16/88/b663a136d1083aeda965dfb58206ee6437c7920df7615415c4407169cbee/trio-0.11.0-py3-none-any.whl (289kB)
Requirement already satisfied: idna in /usr/local/lib/python3.6/site-packages (from trio==0.11.0->-r requirements.txt (line 1)) (2.8)
Collecting outcome (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/b9/3e/88cf8cbc591682e4ef27f61044496ca66733210b57ce01000ffec8b29d88/outcome-1.0.0-py2.py3-none-any.whl
Collecting async-generator>=1.9 (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/71/52/39d20e03abd0ac9159c162ec24b93fbcaa111e8400308f2465432495ca2b/async_generator-1.10-py3-none-any.whl
Collecting attrs>=18.2.0 (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/23/96/d828354fa2dbdf216eaa7b7de0db692f12c234f7ef888cc14980ef40d1d2/attrs-19.1.0-py2.py3-none-any.whl
Collecting contextvars>=2.1; python_version < "3.7" (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/83/96/55b82d9f13763be9d672622e1b8106c85acb83edd7cc2fa5bc67cd9877e9/contextvars-2.4.tar.gz
Collecting sniffio (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/b3/82/4bd4b7d9c0d1dc0fbfbc2a1e00138e7f3ab85bc239358fe9b78aa2ab586d/sniffio-1.1.0-py3-none-any.whl
Collecting sortedcontainers (from trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/13/f3/cf85f7c3a2dbd1a515d51e1f1676d971abe41bba6f4ab5443240d9a78e5b/sortedcontainers-2.1.0-py2.py3-none-any.whl
Collecting immutables>=0.9 (from contextvars>=2.1; python_version < "3.7"->trio==0.11.0->-r requirements.txt (line 1))
  Downloading https://files.pythonhosted.org/packages/e3/91/bc4b34993ef77aabfd1546a657563576bdd437205fa24d4acaf232707452/immutables-0.9-cp36-cp36m-manylinux1_x86_64.whl (91kB)
Building wheels for collected packages: contextvars
  Building wheel for contextvars (setup.py): started
  Building wheel for contextvars (setup.py): finished with status 'done'
  Stored in directory: /home/runner/.cache/pip/wheels/a5/7d/68/1ebae2668bda2228686e3c1cf16f2c2384cea6e9334ad5f6de
Successfully built contextvars
Installing collected packages: attrs, outcome, async-generator, immutables, contextvars, sniffio, sortedcontainers, trio
Successfully installed async-generator-1.10 attrs-19.1.0 contextvars-2.4 immutables-0.9 outcome-1.0.0 sniffio-1.1.0 sortedcontainers-2.1.0 trio-0.11.0
nursery end
Traceback (most recent call last):
  File "/home/runner/.local/lib/python3.6/site-packages/trio/_core/_run.py", line 1430, in run
    run_impl(runner, async_fn, args)
  File "/home/runner/.local/lib/python3.6/site-packages/trio/_core/_run.py", line 1579, in run_impl
    runner.task_exited(task, final_outcome)
  File "/home/runner/.local/lib/python3.6/site-packages/trio/_core/_run.py", line 1050, in task_exited
    task._cancel_stack[-1]._remove_task(task)
  File "/home/runner/.local/lib/python3.6/site-packages/trio/_core/_run.py", line 347, in _remove_task
    self._tasks.remove(task)
KeyError: <Task '__main__.main' at 0x7fcbea2a7198>

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

Traceback (most recent call last):
  File "main.py", line 14, in <module>
    trio.run(main)
  File "/home/runner/.local/lib/python3.6/site-packages/trio/_core/_run.py", line 1436, in run
    ) from exc
trio.TrioInternalError: internal error in trio - please file a bug!

Closing comment (2019-05-17T03:03:59Z): Welcome, and thanks for both reporting this and explaining the unusual circumstances behind it!
I'm going to call this a duplicate of #882 -- what you're doing violates Trio's invariants and we're not interested in supporting it per se, but it's also the case that the error you get from this category of misbehavior is quite confusing and ought to be cleaned up.
You can implement your own coroutine runner on top of Trio, like you're starting to do here -- see trio-asyncio's allow_asyncio() function for an example -- but it's a sufficiently expert-level trick that it's not covered in the documentation. In general Trio expects you to adhere to the same invariants that a native await statement would satisfy: run each coroutine to completion in the same context you started it in. In this case, sleep() is implemented using a cancel scope, so you're effectively doing:

enter nursery (in main())
enter cancel scope (in the first tick of the sleep() coroutine)
leave nursery (in main())
leave cancel scope (as a result of the sleep() coroutine being unwound when the last reference to it is dropped at the end of main())

which doesn't respect the implicit stack that underlies the fundamental mechanics of nurseries and cancel scopes. Trio expects that if you enter the cancel scope after you enter the nursery, then you leave the cancel scope before you leave the nursery, just as would happen naturally if you used await trio.sleep(...).
Note that the Trio yield protocol (the particular nature of what you get out of a call to send() and what Trio expects you to provide in return) is not public API and is subject to change. If you want to poke at it anyway (which I respect!), take a look at trio/_core/_traps.py.