2014-06-08 6 views
4

Play 응용 프로그램에 포함시키고 싶은 계산 알고리즘이 오래 있습니다.타임 아웃 후 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")) 
    } 

} 

예제는 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 { 포크 조인 실행기 { 병렬 팩터 = 200 } } = 20 병렬 맥스} 바로 – Labra