anyio: `BaseException` not returned though `blockingportal.start_task_soon(...).result()`
Take this code:
async def test() -> None:
raise KeyboardInterrupt
# Instead of the above raise statement, we can use prompt_toolkit to read
# input. Pressing control-c raises a `KeyboardInterrupt` here. (That's not
# the default SIGINT handler, which would be handled in the main thread.)
from prompt_toolkit import PromptSession # pip install prompt-toolkit
await PromptSession().prompt_async(">")
# Instead of `KeyboardInterrupt`, raising `BaseException` yields similar
# results.
raise BaseException("test")
def main() -> None:
with start_blocking_portal() as portal:
portal.start_task_soon(test).result()
# We never get here:
# - Either the code just hangs,
# - the portal gets killed
print("------")
portal.start_task_soon(test).result()
if __name__ == "__main__":
main()
The outcome of this is very nondeterministic.
Sometimes it hangs. Sometimes we get a short traceback. Sometimes a very long traceback. Usually the portal gets killed and becomes unusable.
What I’d expect is that any exception raised by our test
function is returned through the concurrent.futures.Future
, not killing the portal. A KeyboardInterrupt
is a valid outcome of an async function and should not kill the portal.
What worries me most is that it’s nondeterministic, and that it sometimes hangs, as if there is a race condition.
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Comments: 17 (12 by maintainers)
Commits related to this issue
- Catch RuntimeError in finalization of start_blocking_portal() Fixes #471. — committed to agronholm/anyio by agronholm 2 years ago
- Catch RuntimeError in finalization of start_blocking_portal() (#472) Fixes #471. — committed to agronholm/anyio by agronholm 2 years ago
- Catch RuntimeError in finalization of start_blocking_portal() (#472) Fixes #471. (cherry picked from commit 446e246158f1f235190e88148c2dbf87bda70043) — committed to agronholm/anyio by agronholm 2 years ago
I’d expect an
Exception
to be propagated into the Future, yes. But not aBaseException
. The latter should always be re-raise
d (except for catching cancellations obviously); if there’s a Future to be signalled along the way, fine, but please feed a newException
-derived object to it. Not the original.