1

Suppose I've got a function fab: A => Future[B] and want it to return a new future that complete before a deadline. So I am writing a new function deadlined like this

def deadlined[B](fut: => Future[B], deadline: Deadline): Future[B] = ???

Now I am using java.util.Timer but could use ScheduledThreadPoolExecutor as suggested. The best solution though is probably a wrapper to abstract out the scheduling implementation and mock it in tests as suggested in comments.

object Deadlined {

  private val timer = new java.util.Timer() // todo: replace it with a wrapper

  def apply[B](fut: => Future[B], deadline: Deadline)(implicit ec: ExecutionContext): Future[B] = {
    val promise = Promise[B]()
    val timerTask = new java.util.TimerTask {
      override def run(): Unit = promise.failure(new Exception(s"$deadline is exceeded"))
    }
    timer.schedule(timerTask, deadline.timeLeft.toMillis)
    fut.transform { result =>
      timerTask.cancel()
      result match {
        case Success(b) => promise.success(b)
        case Failure(t) => promise.failure(t)
      }
      result
    }
    promise.future
  }
}

Does it make sense ? I wonder also how to factor out a common part of Deadlined and Delayed from the response to my previous question.

Michael
  • 37,415
  • 63
  • 167
  • 303

1 Answers1

2

I'd probably do something similar to the following, so I could add a deadline to any Future (YMMV, Caveat Emptor, etc):

import scala.concurrent.{Future, ExecutionContext}
import scala.concurrent.duration.FiniteDuration
import java.util.{Timer, TimerTask}

implicit class DeadlineFuture[T](future: Future[T]) {
  def deadline(d: FiniteDuration)(implicit timer: Timer): Future[T] = {
    if (future.isCompleted) future
    else {
      val promise = Promise[T]()
      val timerTask = new TimerTask {
        override def run(): Unit = promise.tryFailure(new Exception(s"$d is exceeded"))
      }
      timer.schedule(timerTask, d.toMillis)
      future.onComplete(_ => timerTask.cancel())(ExecutionContext.parasitic)
      promise.completeWith(future).future
    }
  }
}

// Usage:
Future.never.deadline(5.seconds).onComplete(println)
Viktor Klang
  • 25,896
  • 7
  • 48
  • 66
  • Thanks. Why `tryFailure` rather than `failure` on the promise ? – Michael Apr 10 '21 at 20:07
  • 1
    Because if the promise gets completed by the future then `promise.failure(…)` in the tmer task will throw an execption. (there's a race between cancelling the TimerTask and the TimerTask trying to set the failure. – Viktor Klang Apr 10 '21 at 20:08
  • Oh, I see. Thank you. – Michael Apr 10 '21 at 20:10
  • Is it correct that `if (future.isCompleted) future` is optimization and the function works without it ? – Michael Apr 11 '21 at 08:38
  • Indeed, Michael—and under certain situations you'll want to reduce the pressure on scheduling TimerTasks when they are known to not be needed. – Viktor Klang Apr 11 '21 at 16:14
  • Sure ! Since it should be probably a _library_ code it should be optimized. – Michael Apr 11 '21 at 16:44
  • One more question: do you use `parasitic` to execute ` timerTask.cancel()` on the same thread because the `cancel` is quick and it's not worth it to switch threads for it ? – Michael May 04 '21 at 07:08
  • @Michael Yes. The alternative would be to have the method take an implicit ExecutionContext. – Viktor Klang May 04 '21 at 11:51
  • Thanks, got it. BTW "parasitic" looks a bit weird. I am about the word choice. – Michael May 04 '21 at 12:34
  • @Michael As you can imagine, a lot of thought went into the naming of that. It needs to both reflect what's happening, but in this case it should also have a negative sounding name, since it can introduce a lot of problems if used incorrectly. – Viktor Klang May 04 '21 at 13:53
  • Thanks for the explanation. I am sure this name was deliberately chosen and the negative connotation is not chosen by chance or by mistake. That's why I am asking :)) – Michael May 04 '21 at 13:58
  • @Michael No problem at all, I'm glad you asked! – Viktor Klang May 04 '21 at 18:32