MultiError sad story
Closed this issue · 2 comments
I know there are plans to have nursery raise MultiError exclusively, but I'll share this story.
A nursery is wrapped in a try/catch naively catching a bare exception. Works fine, except when an enclosing move_on_after() happens to reach its deadline simultaneously with an exception within the nursery.
What made it so hard to debug is that the final exception trace has no mention of MultiError, only the bare exception. Which made it seem like the except
clause was mysteriously ignored.
Here is the repro I used to understand this:
from random import uniform
import trio
count = 0
async def raiser():
await trio.sleep(30 / 1000)
raise ValueError('foo')
async def async_main():
global count
while True:
count += 1
with trio.move_on_after(uniform(20, 40) / 1000):
try:
# The following nursery will sometimes leak a ValueError
# despite the try/except.
async with trio.open_nursery() as nursery:
nursery.start_soon(raiser)
await trio.sleep_forever()
# If replace with a direct call to raiser() it doesn't occur.
#await raiser()
except ValueError:
pass
except BaseException as e:
print(type(e), e)
raise
try:
trio.run(async_main)
except KeyboardInterrupt:
pass
finally:
print('count:', count)
<class 'trio.MultiError'> Cancelled(), Cancelled(), Cancelled()
<class 'trio.MultiError'> Cancelled(), Cancelled(), Cancelled()
...
<class 'trio.MultiError'> ValueError('foo',), Cancelled()
count: 29
Traceback (most recent call last):
File "main.py", line 30, in <module>
trio.run(async_main)
File "/.../site-packages/trio/_core/_run.py", line 1337, in run
raise runner.main_task_outcome.error
File "main.py", line 27, in async_main
raise
File "/.../site-packages/trio/_core/_ki.py", line 165, in wrapper
return fn(*args, **kwargs)
File "/.../site-packages/trio/_core/_run.py", line 260, in __exit__
raise remaining_error_after_cancel_scope
File "main.py", line 20, in async_main
await trio.sleep_forever()
File "/.../site-packages/trio/_core/_run.py", line 397, in __aexit__
raise combined_error_from_nursery
File "main.py", line 8, in raiser
raise ValueError('foo')
ValueError: foo
(FYI @belm0 you can syntax-highlight code blocks for python
and python-traceback
)
Hmm, yeah, it sounds like this is exactly the kind situation that MultiError v2 should help with. So far though it's been motivated by theoretical arguments; I think this is the first report we've had of someone hitting it in the real world. Sorry you hit that, it sounds unpleasant! But thank you for writing it up, it's a compelling argument that we're on the right track with #611 :-).
Since it sounds like the solution here is already included in #611, I'm going to close this and link to it there.