2011-06-03 7 views
14

He estado trabajando mi respuesta al Is there a standard Scala function for running a block with a timeout?, y me he encontrado con un problema si se lanza una excepción en un futuro.¿Cómo puedo obtener las excepciones lanzadas en un Scala Future?

def runWithTimeout[T](timeoutMs: Long)(f: => T) : Option[T] = { 
    awaitAll(timeoutMs, future(f)).head.asInstanceOf[Option[T]] 
    } 

Para que

runWithTimeout(50) { "result" } should equal (Some("result")) 
runWithTimeout(50) { Thread.sleep(100); "result" } should equal (None) 

Pero si tiro una excepción en mi bloque que no se escape, pero se traga - por lo que la siguiente falla con "..no excepción fue arrojado"

intercept[Exception] { 
    runWithTimeout(50) { throw new Exception("deliberate") } 
}.getMessage should equal("deliberate") 

SYSERR tiene un seguimiento de la pila con el mensaje

<function0>: caught java.lang.Exception: deliberate 

pero no puedo encontrar el lugar en el tiempo de ejecución de Scala que se imprime.

Además de envolver f en otro bloque que detecta excepciones y las propaga si se lanzan, ¿hay alguna forma de persuadir a awaitAll y/o Future para lanzar?

+0

es probable que sea impreso porque se hacía pasar al de la rosca [UncaughtExceptionHandler] (http://download.oracle.com/javase/6/docs/api/java/lang/Thread.UncaughtExceptionHandler.html). Podrías configurar tu propio controlador, pero eso aún no te permitiría lanzar la excepción en un hilo diferente. –

+1

Eche un vistazo a los futuros de Fingales (https://github.com/twitter/finagle), busque "Tiempo de espera" y Akka http://akka.io/docs/akka/1.1.2/scala/futures.html – oluies

Respuesta

14

Respuesta corta: no.

Las excepciones no hacen lo que usted desea cuando trabaja en un contexto enhebrado, porque desea conocer la excepción en la persona que llama, y ​​la excepción ocurre en el hilo del futuro.

En su lugar, si desea saber cuál fue la excepción, debe devolver un Either[Exception,WhatYouWant] - por supuesto, debe detectar esa excepción en el futuro y empaquetarla.

scala> scala.actors.Futures.future{ 
    try { Right("fail".toInt) } catch { case e: Exception => Left(e) } 
} 
res0: scala.actors.Future[Product with Serializable with Either[Exception,Int]] = <function0> 

scala> res0() // Apply the future 
res1: Product with Serializable with Either[Exception,Int] = 
     Left(java.lang.NumberFormatException: For input string: "fail") 
+0

El 'Future.get()' de Java arroja 'ExecutionException', en el que envuelve cualquier excepción en el código de ejecución. Ese fue mi modelo aquí. –

+0

@Duncan McGregor: no sé cómo lo logra Java, pero si se hace como una biblioteca no hay más remedio que hacer que el hilo tome la excepción, empaquetarlo y tratarlo en el otro extremo. Supongo que Java lo hace por usted, ya que no tiene ningún mecanismo general para permitir esto; Scala hace que lo hagas tú mismo (usando el mecanismo general suministrado para tales cosas). –

+0

Gracias - ¡O parece otro idioma que aprenderé! –

-1

se debe redefinir el método exceptionHandler con el fin de capturar las excepciones. Entonces, su opción es definir su propio método future para que cree un MyFutureActor con exceptionHandler.

EDITAR: FutureActor es privado, por lo que la subclasificación no es posible.

Otra opción es utilizar enlaces para saber cuándo ocurrieron las excepciones.

Sin embargo, creo que el enfoque de Rex Kerr es mejor: simplemente ajuste la función en algo que atrape la Excepción. Lástima future ya no hace eso.

+0

Me gustó el sonido de esto, pero FutureActor es privado y lo suficientemente complicado que la reproducción sería peluda. –

2

trabajando mi camino a través de la sugerencia de @Rex Kerr, he creado

object Timeout { 

    val timeoutException = new TimeoutException 

    def runWithTimeout[T](timeoutMs: Long)(f: => T) : Either[Throwable, T] = { 
    runWithTimeoutIgnoreExceptions(timeoutMs)(exceptionOrResult(f)) match { 
     case Some(x) => x 
     case None => Left(timeoutException) 
    } 
    } 

    def runWithTimeout[T](timeoutMs: Long, default: T)(f: => T) : Either[Throwable, T] = { 
    val defaultAsEither: Either[Throwable, T] = Right(default) 
    runWithTimeoutIgnoreExceptions(timeoutMs, defaultAsEither)(exceptionOrResult(f)) 
    } 

    def runWithTimeoutIgnoreExceptions[T](timeoutMs: Long)(f: => T) : Option[T] = { 
    awaitAll(timeoutMs, future(f)).head.asInstanceOf[Option[T]] 
    } 

    def runWithTimeoutIgnoreExceptions[T](timeoutMs: Long, default: T)(f: => T) : T = { 
    runWithTimeoutIgnoreExceptions(timeoutMs)(f).getOrElse(default) 
    } 

    private def exceptionOrResult[T](f: => T): Either[Throwable, T] = 
    try { 
     Right(f) 
    } catch { 
     case x => Left(x) 
    } 
} 

modo que

@Test def test_exception { 
    runWithTimeout(50) { "result" }.right.get should be ("result") 
    runWithTimeout(50) { throw new Exception("deliberate") }.left.get.getMessage should be ("deliberate") 
    runWithTimeout(50) { Thread.sleep(100); "result" }.left.get should be (Timeout.timeoutException) 

    runWithTimeout(50, "no result") { "result" }.right.get should be ("result") 
    runWithTimeout(50, "no result") { throw new Exception("deliberate") }.left.get.getMessage should be ("deliberate") 
    runWithTimeout(50, "no result") { Thread.sleep(100); "result" }.right.get should be ("no result") 

} 

Una vez más, soy un poco novato Scala, por lo que daría la bienvenida a la retroalimentación.

10

responsabilidad: yo trabajo para Typesafe

O .... usted podría utilizar Akka y que le daría lo que quiere sin tener que ir a través de aros para ello.

val f: Future[Int] = actor !!! message 

Entonces

f.get 

tirará la excepción de que sucedió en el actor

f.await.exception 

le dará una opción [Throwable]

+0

Es bueno saber que no estaba tan fuera de base pensando que debería ser posible. –

2

scala.concurrent.ops.future incluye el manejo de excepciones.

Por lo tanto, en lugar de importar scala.actors.Futures.future, importe scala.concurrent.ops.future instead.

Ese simple cambio en el que importa la importación provocará que la llamada de la persona que llama vuelva a lanzar la excepción. ¡Funciona genial!

+0

Parece que 'scala.concurrent.ops.future' se eliminó en Scala 2.11 y, por lo tanto, esta respuesta ya no se aplica: https://github.com/scala/scala/commit/67d7e26657a0a52e2bd5dc46bd1bbedda52d2dc0#L4L41 –

0

O utilice Future.liftTryTry, se convierte desde Future[Object] a Future[Try[Object]], y se puede coincidir en el Try[Object] y compruebe si hay una excepción case Throw(e) y log/salida con gracia

Cuestiones relacionadas