-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add RerunnableTimer and RerunnableClock implementations
- Loading branch information
Showing
4 changed files
with
145 additions
and
0 deletions.
There are no files selected for viewing
24 changes: 24 additions & 0 deletions
24
effect/src/main/scala/io/catbird/util/effect/RerunnableClock.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
46
effect/src/main/scala/io/catbird/util/effect/RerunnableTimer.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
) | ||
} |
38 changes: 38 additions & 0 deletions
38
effect/src/test/scala/io/catbird/util/effect/RerunnableClockSuite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |
37 changes: 37 additions & 0 deletions
37
effect/src/test/scala/io/catbird/util/effect/RerunnableTimerSuite.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
} |