Skip to content

Commit

Permalink
Add timeoutOption
Browse files Browse the repository at this point in the history
  • Loading branch information
adamw committed Jul 24, 2023
1 parent 9b17abe commit 465c871
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 3 deletions.
14 changes: 11 additions & 3 deletions core/src/main/scala/ox/race.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,18 @@ import scala.concurrent.TimeoutException
import scala.concurrent.duration.FiniteDuration
import scala.util.{Failure, Success, Try}

/** A `Some` if the computation `t` took less than `duration`, and `None` otherwise. */
def timeoutOption[T](duration: FiniteDuration)(t: => T): Option[T] =
raceSuccess(Some(t))({ Thread.sleep(duration.toMillis); None })

/** The result of computation `t`, if it took less than `duration`, and a [[TimeoutException]] otherwise.
* @throws TimeoutException
* If `t` took more than `duration`.
*/
def timeout[T](duration: FiniteDuration)(t: => T): T =
raceSuccess(Right(t))({ Thread.sleep(duration.toMillis); Left(()) }) match
case Left(_) => throw new TimeoutException(s"Timed out after $duration")
case Right(v) => v
timeoutOption(duration)(t).getOrElse(throw new TimeoutException(s"Timed out after $duration"))

/** Returns the result of the first computation to complete successfully, or if all fail - throws the first exception. */
def raceSuccess[T](fs: Seq[() => T]): T =
scoped {
val result = new ArrayBlockingQueue[Try[T]](fs.size)
Expand All @@ -27,6 +34,7 @@ def raceSuccess[T](fs: Seq[() => T]): T =
takeUntilSuccess(None, fs.size)
}

/** Returns the result of the first computation to complete (either successfully or with an exception). */
def raceResult[T](fs: Seq[() => T]): T = raceSuccess(fs.map(f => () => Try(f()))).get // TODO optimize

/** Returns the result of the first computation to complete successfully, or if all fail - throws the first exception. */
Expand Down
1 change: 1 addition & 0 deletions core/src/main/scala/ox/syntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ object syntax:
extension [T](f: => T)(using Ox)
def fork: Fork[T] = ox.fork(f)
def timeout(duration: FiniteDuration): T = ox.timeout(duration)(f)
def timeoutOption(duration: FiniteDuration): Option[T] = ox.timeoutOption(duration)(f)
def scopedWhere[U](fl: ForkLocal[U], u: U): T = fl.scopedWhere(u)(f)
def uninterruptible: T = ox.uninterruptible(f)
def parWith[U](f2: => U): (T, U) = ox.par(f)(f2)
Expand Down
13 changes: 13 additions & 0 deletions core/src/test/scala/ox/RaceTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,19 @@ class RaceTest extends AnyFlatSpec with Matchers {
trail.trail shouldBe Vector("no timeout", "done")
}

"timeoutOption" should "short-circuit a long computation" in {
val trail = Trail()
val result = timeoutOption(1.second) {
Thread.sleep(2000)
trail.add("no timeout")
}

trail.add(s"done: $result")
Thread.sleep(2000)

trail.trail shouldBe Vector("done: None")
}

it should "race a slower and faster computation" in {
val trail = Trail()
val start = System.currentTimeMillis()
Expand Down

0 comments on commit 465c871

Please sign in to comment.