The more general question here is: in production-quality code, does one need to be concerned with exceptions produced by the ExecutionContext or other concurrency infrastructure, outside of the execution of the future's body? For example, if some failure happens with the thread pool, will I see an exception returned in a future that failed to execute because of it?
This, in turn, is leading toward how should error handling be done with futures. I'm on-board with the general advice that errors should be returned, not thrown, using e.g. either Either or scalactic's Or. But this seems greatly complicated if, when a future is invoked, one needs to factor in exceptions from the infrastructure, even when everything else is written in an exception-free or exception-wrapping manner. But I won't ask for advice on that - it will get this post closed as "too broad", I think. :=(
If an exception occurs while executing a Future
but not in the body of it, I don't think it can possibly be returned within that Future
. The implementation of Future.apply
submits a PromiseCompletingRunnable
to the executor. It looks like this:
class PromiseCompletingRunnable[T](body: => T) extends Runnable {
val promise = new Promise.DefaultPromise[T]()
override def run() = {
promise complete {
try Success(body) catch { case NonFatal(e) => Failure(e) }
}
}
}
Notice that the underlying Promise
is completed with using the try/catch block that executes the body of Future.apply
. Should an exception occur within the above Runnable
(doesn't seem likely), or the executor outside of that try/catch block, it will not be wrapped in the returned Future
. If some exception occurs within the executor, the more likely scenario is that it's some fatal error. Worse, it could mean the Future
will just never be completed.
If it's a thread pool that's causing problems, there really isn't much you can do about that, outside of finding a better one or identifying the underlying problem (too many threads ?). And there's even less (if anything) you can do about it at run-time. Really, that would just mean that the underlying concurrency API is very broken, using too much memory, etc.
Yes and No ... ok maybe we need to elaborate on that.
If we talk "production-quality code", i.e. code that costs large amounts of money if it doesn't work we can not assume, that the infrastructure (runtime, libraries and so on) just work. Because they don't. Libraries have bugs and even if they don't affect us, they rely on limited resources like memory, file handles and in the case of concurrency stuff: threads.
So one thing one should realize at some point is: even the most trivial line of code might fail. So that is the Yes part: Yes you should concern yourself with that possibility.
Next to the No part: You write
errors should be returned, not thrown, using e.g. either Either or scalactic's Or
This is true for exceptions that you kind of expect. Thing that are really likely to fail. Say in more then 1 in 100 000 executions. (Yes I pulled that number out of my hat, that I'm not even wearing). In such cases adding explicit exception handling is the appropriate thing to do, and the advice you mentioned applies.
But it does not apply for all the other possible failure modes. If you'd try to handle them with this approach, you wouldn't be able to find your main code between all the exception handling. And you would probably add lots of bugs in the process.
Instead, have some general strategy how to handle arbitrary exceptions in your code. For these cases Exceptions are the correct tool to use. For this strategy simpler is better, since you really don't know in what kind of circumstance this code will get executed.
The typical approach in web applications is to have one try catch block around a complete request, that logs the exception and returns some error page.
A more elaborate approach is that of actor frameworks like Akka, which have Actors for doing the work. If such an Actor throws an exception, the Actor will die and be replaced by a new one (with various ways to control the exact behavior).
Another approach, that gets combined with the two approaches above, is to have multiple instances of your application run on different machines, so problems on one machine don't affect the others.
As you can see the approaches to handle exceptions inside your infrastructure, including but not limited to the library executing your futures is very different from the exception handling used for handling "normal" failure modes of your futures.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With