0
votes

My question is somewhat related to this question about default error handlers for Scala futures.

The twist is, I want to install a default error handler that only kicks in if no onFailure (or onComplete) callback has been installed on the Future. I think this would be quite useful because i have written code where my Futures fail silently leaving me confused about what's going on. A fallback error handler would enable me to easily identify errant Futures for which an appropriate onFailure callback was never defined.

Below is a code sample which illustrates where this could be useful.

  import scala.concurrent.ExecutionContext.Implicits.global
  import scala.concurrent._
  import scala.language.implicitConversions
  import scala.language.postfixOps

  def futureWithLogging[T](fut: Future[T]) =
    fut.transform(identity, { t: Throwable =>
      Console.err.println(s"failed with $t")
      t
    })


  {
    val f = futureWithLogging ( Future {
      Thread.sleep(1000)
      throw new scala.RuntimeException("")
    } )

    f.onFailure{ case err:Throwable => {println("custom error message"); err.printStackTrace()} }

    println(s"future value is: ${f.value}")
    Thread.sleep(1000)
    println(s"future value is now: ${f.value}")
    Thread.sleep(1000)
  }

Notice that I have a futureWithLogging function which installs some default error handling behavior on any future that you pass into the function. Now, if I forget to install an onFailure on a future this is very handy. But in the code above you see that I have an onFailure handler is installed on Future 'f'. The result is that I get noise in my log. I see two error messages:

  failed with <exception>  

and

   custom error message...

Where in this case I only want to see the second one

Any guidance you could provide is much appreciated !

1

1 Answers

0
votes

The nearest I have come to what you want, is for any specific failure handler to wrap the exception in a special "HandledException" wrapper, that the default failure handler can deal with specially (ignore, or print a suitable message, or whatever). Eg:

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent._
import scala.language.implicitConversions
import scala.language.postfixOps

case class HandledException(inner: Throwable) extends RuntimeException(inner)

def futureWithLogging[T](fut: Future[T]) =
  fut.transform(identity, { t: Throwable =>
    Console.err.println(s"failed with $t")
    HandledException(t)
  })


{
  val f = futureWithLogging ( Future {
    Thread.sleep(1000)
    throw new scala.RuntimeException("")
  } )

  f.onFailure {
    case HandledException(inner) => println("Already handled: "+inner)
    case err:Throwable => {println("custom error message"); err.printStackTrace()}
  }

  println(s"future value is: ${f.value}")
  Thread.sleep(1000)
  println(s"future value is now: ${f.value}")
  Thread.sleep(1000)
}

From here, you could add, say, an issue ID to the "HandledException" class, which can be printed out where the initial exception is first caught, then passed up to the front end (and possibly printed out short-hand on the way by things like the default handler) and presented to the user with a message like "Sorry, we hit an error - you can email our support crew, quoting issue ID '...' for help" or whatever you like. This ID could be stored in a DB, traced through log files (maybe put into the logging framework's diagnostic context, if appropriate), etc.