Related to our previous post about catching top-level
Actor
exceptions, we also have a few utility classes for working with Future
s. These do things such as offer baked-in exception logging, establish SLA-timeouts on futures, and give the ability to interrupt a running code block that is in a future.
Check out all of the code together on Github.
SafeFuture
SafeFuture
is a Future
that simply binds an onFailure
to any future passed in to deal with hard failures. Future
failures can and should be handled, but for fire-and-forget and map-chaining, it’s often easy to not handle failures explicitly. The result is a exception that goes nowhere — including failure to your go to your logs. Mysterious failure is not good, so SafeFuture
lets you design an application level failure monitoring strategy. This won’t save your result, but at least you’ll be aware it failed.
As an added bonus, there’s a second apply
in SafeFuture
that lets you name the future for logging purposes.
class SafeFuture[+T](future: Future[T], name: Option[String] = None)(implicit executor: ExecutionContext) extends Future[T] { future match { case _: SafeFuture[_] => case dangerousFuture => dangerousFuture.onFailure { case cause: Throwable => cause.printStackTrace() // should always work, to stderr try { // Should work if the Logger is up: Logger(getClass).error("[SafeFuture] Failure of future" + name.map(": " + _).getOrElse(""), cause) // ... and add your custom monitoring. Ideas: emails, healthcheck API calls, etc. } catch { case _: Throwable => // tried our best. } } } // Just a wrapper around Future, so we can match on SafeFuture explicitly. def onComplete[U](func: (Try[T]) => U)(implicit executor: ExecutionContext): Unit = future.onComplete(func) def isCompleted: Boolean = future.isCompleted def value: Option[Try[T]] = future.value def ready(atMost: Duration)(implicit permit: CanAwait): this.type = { future.ready(atMost); this } def result(atMost: Duration)(implicit permit: CanAwait): T = future.result(atMost) } object SafeFuture { // Replicate Future {} helper def apply[T](func: => T)(implicit executor: ExecutionContext) = new SafeFuture(Future { func }) def apply[T](name: String)(func: => T)(implicit executor: ExecutionContext) = new SafeFuture(Future { func }, Some(name)) } |
TimeoutFuture
TimeoutFuture
lets you establish a SLA timeout for a Future
. Simply, if that time passes and the future has not resolved, it resolves as a failure with a TimeoutException
. Typically, this should be explicitly handled by your application — supplying a default value, returning an error, etc.
For efficiency, we use netty’s HashWheelTimer
. This does not give explicit guarantees about exactly when it runs, and instead provides a best-effort timer. So: It’s much lighter than a scheduler, but less accurate.
object TimeoutFuture { def apply[T](future: Future[T], onTimeout: => Unit = Unit)(implicit ec: ExecutionContext, after: Duration): Future[T] = { val timer = new HashedWheelTimer(10, TimeUnit.MILLISECONDS) val promise = Promise[T]() val timeout = timer.newTimeout(new TimerTask { def run(timeout: Timeout){ onTimeout promise.failure(new TimeoutException(s"Future timed out after ${after.toMillis}ms")) } }, after.toNanos, TimeUnit.NANOSECONDS) // does not cancel future, only resolves result in approx duration. Your future may still be running! Future.firstCompletedOf(Seq(future, promise.future)).tap(_.onComplete { case result => timeout.cancel() }) } } |
CancelableFuture
CancelableFuture
creates a future that can be cancelled, from a blocking code block. This is not usually for SLA timeout guarantees like above. Rather, it’s for when you have a complex long-running blocking bit of code that you want to be able to kill. So, this returns a method that, once called, will harshly interrupt the thread and stop the code. It’s nasty, be careful with it. Example non-deterministic usage:
val (fut, cancel) = CancellableFuture(Thread.sleep((Math.random*2000).toInt tap println)) Thread.sleep(1000) val wasCancelled = cancel() println("wasCancelled: " + wasCancelled) fut.onFailure { case ex: Throwable => println("failed: " + ex.getClass) } fut.onSuccess { case i => println("success!" + i) } |
object CancelableFuture { def apply[T](fun: => T)(implicit ex: ExecutionContext): (Future[T], () => Boolean) = { val promise = Promise[T]() val future = promise.future val threadRef = new AtomicReference[Thread](null) promise tryCompleteWith SafeFuture { // If you want, swap with normal `Future` val t = Thread.currentThread threadRef.synchronized { threadRef.set(t) } try fun finally { threadRef.synchronized(threadRef.set(null)) } } (future, () => { threadRef.synchronized { Option(threadRef getAndSet null) foreach { _.interrupt() } } promise.tryFailure(new CancellationException) }) } } |
All of this code together (with necessary imports) is on Github. Have any other useful utility classes for working with Akka and Futures? Let us know!