Skip to content

Commit

Permalink
Add RerunnableTimer and RerunnableClock implementations
Browse files Browse the repository at this point in the history
  • Loading branch information
felixbr committed Mar 16, 2020
1 parent 36e241a commit d36020e
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 0 deletions.
24 changes: 24 additions & 0 deletions effect/src/main/scala/io/catbird/util/effect/RerunnableClock.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package io.catbird.util.effect

import java.util.concurrent.TimeUnit

import cats.effect.Clock
import io.catbird.util.Rerunnable
import scala.Long
import java.lang.System

import scala.concurrent.duration.TimeUnit

object RerunnableClock {

def apply(): RerunnableClock = new RerunnableClock
}

final private[effect] class RerunnableClock extends Clock[Rerunnable] {

override def realTime(unit: TimeUnit): Rerunnable[Long] =
Rerunnable(unit.convert(System.currentTimeMillis(), TimeUnit.MILLISECONDS))

override def monotonic(unit: TimeUnit): Rerunnable[Long] =
Rerunnable(unit.convert(System.nanoTime(), TimeUnit.NANOSECONDS))
}
46 changes: 46 additions & 0 deletions effect/src/main/scala/io/catbird/util/effect/RerunnableTimer.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package io.catbird.util.effect

import cats.effect.{ Clock, Timer }
import io.catbird.util.Rerunnable
import com.twitter.util.Future
import com.twitter.util
import scala.Unit

import scala.concurrent.duration.FiniteDuration

/**
* Can be used to construct a `cats.effect.Timer` instance for `Rerunnable` which let's you delay execution or
* retrieve the current time via `RerunnableClock`.
*
* Usage:
* {{{
* // In a Finagle application
* implicit val timer: Timer[Rerunnable] = RerunnableTimer(com.twitter.finagle.util.DefaultTimer)
*
* // In tests (for instant execution of delays)
* implicit val timer: Timer[Rerunnable] = RerunnableTimer(com.twitter.util.Timer.Nil)
*
* // A dedicated `JavaTimer`
* implicit val timer: Timer[Rerunnable] = RerunnableTimer()
* }}}
*/
object RerunnableTimer {

def apply(implicit twitterTimer: util.Timer): RerunnableTimer = new RerunnableTimer

def apply(): RerunnableTimer = {
implicit val twitterTimer: util.Timer = new util.JavaTimer

new RerunnableTimer
}
}

final private[effect] class RerunnableTimer private (implicit underlyingTimer: util.Timer) extends Timer[Rerunnable] {

override val clock: Clock[Rerunnable] = RerunnableClock()

override def sleep(duration: FiniteDuration): Rerunnable[Unit] =
Rerunnable.fromFuture(
Future.Unit.delayed(util.Duration.fromNanoseconds(duration.toNanos))
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package io.catbird.util.effect

import java.time.Instant
import java.util.concurrent.TimeUnit

import cats.effect.Clock
import com.twitter.util.Await
import io.catbird.util.Rerunnable
import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuite

class RerunnableClockSuite extends FixtureAnyFunSuite {

protected final class FixtureParam {
def now: Instant = Instant.now()

val clock: Clock[Rerunnable] = RerunnableClock()
}

test("Retrieval of real time") { f =>
val result = Await.result(
f.clock.realTime(TimeUnit.MILLISECONDS).map(Instant.ofEpochMilli).run
)

assert(java.time.Duration.between(result, f.now).abs().toMillis < 50)
}

test("Retrieval of monotonic time") { f =>
val result = Await.result(
f.clock.monotonic(TimeUnit.NANOSECONDS).run
)

val durationBetween = Math.abs(System.nanoTime() - result)
assert(TimeUnit.MILLISECONDS.convert(durationBetween, TimeUnit.NANOSECONDS) < 5)
}

override protected def withFixture(test: OneArgTest): Outcome = withFixture(test.toNoArgTest(new FixtureParam))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.catbird.util.effect

import cats.effect.Timer
import org.scalatest.Outcome
import org.scalatest.funsuite.FixtureAnyFunSuite
import com.twitter.util
import com.twitter.util.{ Await, Future }
import io.catbird.util.Rerunnable

import scala.concurrent.duration._

class RerunnableTimerSuite extends FixtureAnyFunSuite {

protected final class FixtureParam {
val twitterTimer: util.Timer = new util.JavaTimer()
}

test("A timer can be used to delay execution") { f =>
implicit val timer: Timer[Rerunnable] = RerunnableTimer(f.twitterTimer)

val result = Await.result(
Future.selectIndex(
Vector(
for {
_ <- Timer[Rerunnable].sleep(100.milliseconds).run
r <- Future.value("slow")
} yield r,
Future.value("fast").delayed(util.Duration.fromMilliseconds(50))(f.twitterTimer)
)
)
)

assert(result == 1) // The first future is delayed for longer, so we expect the second one to win
}

override protected def withFixture(test: OneArgTest): Outcome = withFixture(test.toNoArgTest(new FixtureParam))
}

0 comments on commit d36020e

Please sign in to comment.