2014-06-08 23 views
4

我有一個很長的計算算法,我想要在Play應用程序中使用它。在超時後中斷播放2中的長時間計算

我想添加一個超時值,所以如果計算時間超過一段時間,應該中斷並顯示一些錯誤消息。

看看Handling asynchronous results documentation - handling time-outs它解釋瞭如何創建一個長計算超時。

但是,我注意到,雖然用戶收到超時消息,但計算不會中斷,即日誌消息會一直打印。

如何在超時提高後中斷長計算?

的例子控制器代碼:

object Application extends Controller { 

    def timeout(n:Integer) = Action.async { 
    val futureInt = scala.concurrent.Future { longComputation() } 
    val timeoutFuture = play.api.libs.concurrent.Promise.timeout("Oops", 1.second) 
    Future.firstCompletedOf(Seq(futureInt, timeoutFuture)).map { 
    case i: Int => Ok("Got result: " + i) 
    case t: String => InternalServerError(t) 
    } 
    } 

    def longComputation(): Int = { 
    while (true) { 
     Thread.sleep(1000) 
     Logger.debug("Computing...") 
    } 
    return 0 
    } 

} 

回答

1

爲了實現這一問題的要求,這是必要的,如果它的運行時間超過最大持續時間,以便能夠中斷長時間運行計算。此外,有必要處理控制器操作中斷的可能性。

假設計算涉及多個步驟和/或重複,中斷此計算的一種方法(而不是放棄其結果並使計算繼續運行)是定期檢查計算的當前持續時間是否大於最大值持續時間。

爲了明確說明此計算可能失敗,可以聲明它爲返回Try[T]

然後,操作可以檢查未來成功時計算嘗試的結果,併爲成功或失敗的嘗試生成適當的輸出。

例如:

package controllers 

import play.api._ 
import play.api.libs.concurrent.Akka 
import play.api.libs.concurrent.Execution.Implicits.defaultContext 
import play.api.mvc._ 
import play.api.Play.current 
import scala.concurrent.duration._ 
import scala.concurrent.ExecutionContext 
import scala.concurrent.Future 
import scala.util._ 

object Application extends Controller { 

    def factorial(n: Int) = Action.async { 
    computeFactorial(n, 3.seconds).map { result => 
     result match { 
     case Success(i) => Ok(s"$n! = $i") 
     case Failure(ex) => InternalServerError(ex.getMessage) 
     } 
    } 
    } 

    def computeFactorial(n: BigInt, timeout: Duration): Future[Try[BigInt]] = { 

    val startTime = System.nanoTime() 
    val maxTime = timeout.toNanos 

    def factorial(n: BigInt, result: BigInt = 1): BigInt = { 
     // Calculate elapsed time. 
     val elapsed = System.nanoTime() - startTime 
     Logger.debug(s"Computing factorial($n) with $elapsed nanoseconds elapsed.") 

     // Abort computation if timeout was exceeded. 
     if (elapsed > maxTime) { 
     Logger.debug(s"Timeout exceeded.") 
     throw new ComputationTimeoutException("The maximum time for the computation was exceeded.") 
     } 

     // Introduce an artificial delay so that less iterations are required to produce the error. 
     Thread.sleep(100) 

     // Compute step. 
     if (n == 0) result else factorial(n - 1, n * result) 
    } 

    Future { 
     try { 
     Success(factorial(n)) 
     } catch { 
     case ex: Exception => Failure(ex) 
     } 
    }(Contexts.computationContext) 
    } 

} 

class ComputationTimeoutException(msg: String) extends RuntimeException(msg) 

object Contexts { 
    implicit val computationContext: ExecutionContext = Akka.system.dispatchers.lookup("contexts.computationContext") 
} 

代碼可以是如果它不要求明確標示計算的結果是會犯錯誤的,如果播放的默認異步失敗處理(返回500內部服務器錯誤)更簡潔是足夠了:

object Application extends Controller { 

    def factorial(n: Int) = Action.async { 
    computeFactorial(n, 3.seconds).map { i => Ok(s"$n! = $i") } 
    } 

    def computeFactorial(n: BigInt, timeout: Duration): Future[BigInt] = { 
    val startTime = System.nanoTime() 
    val maxTime = timeout.toNanos 

    def factorial(n: BigInt, result: BigInt = 1): BigInt = { 
     if (System.nanoTime() - startTime > maxTime) { 
     throw new RuntimeException("The maximum time for the computation was exceeded.") 
     } 
     Thread.sleep(100) 
     if (n == 0) result else factorial(n - 1, n * result) 
    } 

    Future { factorial(n) }(Akka.system.dispatchers.lookup("contexts.computationContext")) 
    } 

} 

實例運行提供了一個線程池是從Play使用用於處理HTTP請求的線程池不同的自定義上下文計算。有關更多信息,請參閱Understanding Play thread pools。上下文是在application.conf聲明:

contexts { 
    computationContext { 
    fork-join-executor { 
     parallelism-factor=20 
     parallelism-max = 200 
    } 
    } 
} 

可下載示例,請參見this GitHub project

+0

我想執行你的代碼,但我得到:[RuntimeException:java.lang.ExceptionInInitializerError],我需要在配置中添加一些東西嗎?我已經嘗試了Play 2.2和Play 2.3 ... – Labra

+0

我更新了答案,並鏈接到示例存儲庫。請下載並讓我知道是否有任何問題。 –

+0

謝謝,我注意到只有我需要做的更改是在conf/application中添加以下行。CONF:上下文{ computationContext { 的fork-join-執行{ 並行性係數= 20 並行-MAX = 200 } } } – Labra