StandardErrors are swallowed #239
Replies: 8 comments 7 replies
-
This is definitely easy to get tripped up on, and it's one of the more confusing aspects of Async, IMO, but I think it all makes sense eventually! There's a bit of documentation around exceptions worth reading, you can check this out to get started: https://socketry.github.io/async/guides/asynchronous-tasks/index.html#exception-handling Let me know what you think, and if you have more questions I can try to help, or I'm sure someone else can chime in as well! |
Beta Was this translation helpful? Give feedback.
-
Super helpful - thanks. I was doing something wrong, and I'm still not super happy with what I have to do. My mistake:
Because I was using Fiber.schedule directly I had no access to the exceptions. That's my mistake and it happened because I was migrating to async and failed to convert the schedule calls. What I have to do:
This is sufficient. But I would still prefer something more streamlined:
Or perhaps (if the outer can keep track of the internal fibers)
Having to gather and call wait to re-raise isn't at all intuitive. If I were coming to the code as a new user, I would probably assume the wait was needed to get the fibers to execute - not that it was being used to handle errors! |
Beta Was this translation helpful? Give feedback.
-
I use a barrier for this, since it collects the tasks in a "bucket" I can call |
Beta Was this translation helpful? Give feedback.
-
A task may outlive it's parent so the parent can't naturally be a scope for exception handling. I think ideally, tasks don't raise exceptions as a matter of normal behaviour ("exceptional" cases). Either you are waiting on a task to complete (fan out) or you are "fire and forget". You need to think about what situation you are in and what is the appropriate error handling for your situation. As @trevorturk suggested, using an |
Beta Was this translation helpful? Give feedback.
-
It is such an obvious use case. I should think that passing a function to deal with exceptions would be trivial.
|
Beta Was this translation helpful? Give feedback.
-
The question is who should handle the exception? Elixir/Erlang has the concept of 'let it fail'; Instead of trying to handle all errors in a task, you're encouraged to just let if fail. Tasks can be organised in supervisor trees. If a task fails, a supervisor will restart that task (and others if needed) from a known good state. Requiring each task to handle all StandardErrors means you have to add similar code to every task. If you want tasks higher up in the hierarchy help out when a task fails, it does not seem straightforward to me how to implement that well. |
Beta Was this translation helpful? Give feedback.
-
This is essentially how Barrier is implemented. But note that errors raised in a task will not be re-raised until all previous tasks complete. E.g. if the first task never completes, errors in other tasks will never be re-raised. See #272. |
Beta Was this translation helpful? Give feedback.
-
Related to the swallowing of errors, is the fact that unhandled errors are logged (printed) only if nobody is waiting for the task. But the task starts running as soon as you create it, and you cannot wait for it until you've created it. So whether the warning is printed can seem arbitrary, because it depends on whether the task yields so that the parent task.wait is reached before the task raises an error. In this example, a warning is printed, even though we try to wait for the task: Async do
task = Async do
puts 42**43 % 44
raise RuntimeError.new('ups')
end
task.wait
rescue StandardError
# handle task error
end If the task does anyting that causes it to yield, no warning is printed: Async do
task = Async do
sleep 0.0000001
raise RuntimeError.new('ups')
end
task.wait
rescue StandardError
# handle task error
end |
Beta Was this translation helpful? Give feedback.
-
I'm adding async and I love it so far - with one exception: exceptions. Well, those that are StandardErrors, anyway. Async swallows them.
task.rb:314:
That false says to not re-raise it. So the task exits and says there "might have been" an exception and gives the backtrace - all of which is helpful - but not enough. Ideally I would like it to treat StandardErrors the same way as Exceptions. At the least I want some way to get at the swallowed exceptions so I can re-raise them myself.
I'm happy to code a solution - including passing a parameter to Async do -- Async(error_handling: :reraise) do, or something like that.
Beta Was this translation helpful? Give feedback.
All reactions