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
Exceptionto be propagated into the Future, yes. But not aBaseException. The latter should always be re-raised (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.