-
Notifications
You must be signed in to change notification settings - Fork 235
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tasks that raise an exception crash the entire executor, even when the exception is caught #1098
Comments
…e exception is caught ros2/rclpy#1098 Signed-off-by: Tomoya Fujita <[email protected]>
I think |
The stacktrace is fairly clear:
The exception is first raised in the throwing task (as expected!), then re-raised inside the executor. I'm wondering why we do this? I have the feeling that exceptions raised by asynchronous tasks should only be raised in an |
There is some possibility that this would be improved by #971 (though it needs a rebase). It might be worthwhile to test something like that. |
I encountered the same problem and created a workaround: def schedule_task(coro: Coroutine[None, None, Any], executor: Executor) -> Future:
"""Create a task which can be canceled and does not crash the executor."""
future = Future(executor=executor)
# interrupt any pending spins
future.add_done_callback(lambda _: executor.wake())
executor.create_task(condom_task(future, coro))
return future
@types.coroutine
def coroutine_yield() -> Generator[None, None, None]:
"""Yield control to the executor."""
yield
async def condom_task(future: Future, coro: Coroutine[None, None, Any]) -> None:
"""Redirect exceptions to a future to prevent the executor from seeing them."""
while not future.cancelled():
try:
coro.send(None)
except StopIteration as result:
future.set_result(result.value)
return
except Exception as error:
future.set_exception(error)
return
await coroutine_yield()
# run any cleanup code
try:
coro.close()
except Exception as error:
if isinstance(error.__context__, GeneratorExit):
future.set_exception(error)
else:
raise The workaround involves wrapping the Coroutine with a second one which acts as shield to redirect exceptions from the coroutine away from the executor to a future. |
Maybe we could copy the behavior of |
Any update on this? To me this should catch the Exception: Lines 259 to 261 in ba72a01
What am I missing which causes this issue? |
Just for completeness, here's the equivalent example using just asyncio showing how it differs from rclpy import asyncio
async def throwing_task():
raise FileNotFoundError()
async def test_1():
try:
await throwing_task()
except FileNotFoundError:
pass
return True
async def test_2():
task = asyncio.get_event_loop().create_task(throwing_task())
try:
await task
except FileNotFoundError:
print("Error but should keep going")
return True
asyncio.run(test_1())
print("Got here after test 1")
asyncio.run(test_2())
print("Got here after test 2") $ python3 ./rclpy1080.py
Got here after test 1
Error but should keep going
Got here after test 2 |
I think I misunderstood the issue before. The problem is the Executor raises the exception when it's going to be handled by another task. I'm not totally sure how asyncio accomplishes it, but I think my previous suggestion #1098 (comment) is incorrect |
Bug report
Required Info:
Steps to reproduce issue
Expected behavior
The above code should complete and pass since the
FileNotFoundError
is caught in both casesActual behavior
The executor itself raises an exception on
test_2
which is uncaught and brings down the entire test...Additional information
I was trying to run a number of
async
callbacks in the rclpy executor, and I was raising exceptions in some cases. For some reason, those would bring down the entire node. I tracked it down to these lines:rclpy/rclpy/rclpy/executors.py
Line 724 in f6d652f
I am not sure why the executor is raising the exception of the task 🤔 It might be due to the fact that subscribers, timers etc... are tasks that are never explicitly awaited? I don't have much knowledge about the internals of rclpy but this is very surprising coming from
asyncio
... It is also very surprising to see that the exception is only improperly raised when packaged as an asynchronous task, and not whenawait
ed directly.Thank you!
The text was updated successfully, but these errors were encountered: