Interrupt long calculations in Play 2 after timeout

I have a long calculation algorithm that I want to include in the Play app.

I want to add a timeout value, so if the calculation takes longer, it should be interrupted and some error message is displayed.

Looking at Processing Asynchronous Documentation of Results - Timeout Processing explains how to create a timeout with a lengthy calculation.

However, I noticed that although the user receives a timeout message, the calculation is not interrupted, i.e. journal messages keep printing forever.

How can I interrupt long calculations after the timeout has been raised?

Example controller code:

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
   }

}
+4
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 Internal Server Error) :

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-. . . application.conf:

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

. GitHub .

+1

Source: https://habr.com/ru/post/1543779/


All Articles