0

Trying to execute a function in a given time frame, but if computation fails by TimeOut get a partial result instead of an empty exception.

The attached code solves it.

The timedRun function is from Computation with time limit

Any better approach?.

package ga                                                                                                                                                                                                    


object Ga extends App {                                                                                                                                                                                       

  //this is the ugly...                                                                                                                                                                                       
  var bestResult = "best result";                                                                                                                                                                             
  try {                                                                                                                                                                                                       
    val result = timedRun(150)(bestEffort())                                                                                                                                                                  
  } catch {                                                                                                                                                                                                   
    case e: Exception =>                                                                                                                                                                                      
      print ("timed at = ")                                                                                                                                                                                   
  }                                                                                                                                                                                                           
  println(bestResult)                                                                                                                                                                                         

  //dummy function                                                                                                                                                                                            
  def bestEffort(): String = {                                                                                                                                                                                
    var res = 0                                                                                                                                                                                               
    for (i <- 0 until 100000) {                                                                                                                                                                               
      res = i                                                                                                                                                                                                 
      bestResult = s" $res"                                                                                                                                                                                   
    }                                                                                                                                                                                                         
    " " + res                                                                                                                                                                                                 
  }                                                                                                                                                                                                           

  //This is the elegant part from stackoverflow  gruenewa                                                                                                                                                     
  @throws(classOf[java.util.concurrent.TimeoutException])                                                                                                                                                     
  def timedRun[F](timeout: Long)(f: => F): F = {                                                                                                                                                              
    import java.util.concurrent.{ Callable, FutureTask, TimeUnit }                                                                                                                                            
    val task = new FutureTask(new Callable[F]() {                                                                                                                                                             
      def call() = f                                                                                                                                                                                          
    })                                                                                                                                                                                                        
    new Thread(task).start()                                                                                                                                                                                  
    task.get(timeout, TimeUnit.MILLISECONDS)                                                                                                                                                                  
  }                                                                                                                                                                                                           
}                                                                                                                                                                                                             
Krlos
  • 140
  • 8

2 Answers2

1

I would introduce a small intermediate class for more explicitly communicating the partial results between threads. That way you don't have to modify non-local state in any surprising ways. Then you can also just catch the exception within the timedRun method:

  class Result[A](var result: A)

  val result = timedRun(150)("best result")(bestEffort)                                                                                                                                                                                                                                                                                                                                                                        
  println(result)                                                                                                                                                                                         

  //dummy function                                                                                                                                                                                            
  def bestEffort(r: Result[String]): Unit = {                                                                                                                                                                                
    var res = 0                                                                                                                                                                                               
    for (i <- 0 until 100000) {                                                                                                                                                                               
      res = i                                                                                                                                                                                                 
      r.result = s" $res"                                                                                                                                                                                   
    }                                                                                                                                                                                                         
    r.result = " " + res                                                                                                                                                                                                 
  }

  def timedRun[A](timeout: Long)(initial: A)(f: Result[A] => _): A = {                                                                                                                                                              
    import java.util.concurrent.{ Callable, FutureTask, TimeUnit }
    val result = new Result(initial)
    val task = new FutureTask(new Callable[A]() {                                                                                                                                                             
      def call() = { f(result); result.result }                                                                                                                                                                                          
    })                                                                                                                                                                                                        
    new Thread(task).start()
    try {
      task.get(timeout, TimeUnit.MILLISECONDS)
    } catch {
      case e: java.util.concurrent.TimeoutException => result.result
    }
  }

It's admittedly a bit awkward since you don't usually have the "return value" of a function passed in as a parameter. But I think it's the least-radical modification of your code that makes sense. You could also consider modeling your computation as something that returns a Stream or Iterator of partial results, and then essentially do .takeWhile(notTimedOut).last. But how feasible that is really depends on the actual computation.

Joe K
  • 18,204
  • 2
  • 36
  • 58
  • The returning value must always the number reached (as string... but a number), if calculation ends with no timeout "9999" but if function is timed out it will be a number too... obviously less than 9999. The original code never prints "best result" – Krlos Aug 10 '18 at 22:27
  • sorry 99999 not 9999 – Krlos Aug 10 '18 at 22:42
  • The original code prints "best result" if you time out that thread before the loop executes even once. Same as this code. Anyway you can change it to start at "0" or whatever – Joe K Aug 11 '18 at 06:42
1

First, you need to use one of the solution to recover after the future timed out which are unfortunately not built-in in Scala: See: Scala Futures - built in timeout? For example:

   def withTimeout[T](fut:Future[T])(implicit ec:ExecutionContext, after:Duration) = {
      val prom = Promise[T]()
      val timeout = TimeoutScheduler.scheduleTimeout(prom, after)
      val combinedFut = Future.firstCompletedOf(List(fut, prom.future))
      fut onComplete{case result => timeout.cancel()}
      combinedFut
    }

Then it is easy:

var bestResult = "best result"

val expensiveFunction = Future {
  var res = 0
  for (i <- 0 until 10000) {
    Thread.sleep(10)
    res = i
    bestResult = s" $res"
  }
  " " + res
}

val timeoutFuture = withTimeout(expensiveFunction) recover {
  case _: TimeoutException => bestResult
}

println(Await.result(timeoutFuture, 1 seconds))
proximator
  • 687
  • 6
  • 18