Skip to content

Commit

Permalink
Update to cats-effect 3.0.0-RC2 and implement MonadCancel
Browse files Browse the repository at this point in the history
  • Loading branch information
felixbr committed Feb 24, 2021
1 parent 59ed6ff commit cd50695
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 30 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
val catsVersion = "2.3.0"
val catsEffectVersion = "3.0.0-M2"
val catsEffectVersion = "3.0.0-RC2"
val utilVersion = "20.12.0"
val finagleVersion = "20.12.0"

Expand Down
25 changes: 18 additions & 7 deletions effect/src/main/scala/io/catbird/util/effect/FutureInstances.scala
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
package io.catbird.util.effect

import cats.effect.kernel.{ MonadCancel, Outcome }
import com.twitter.util.{ Future, Monitor }
import io.catbird.util.FutureMonadError
import java.lang.Throwable

import cats.effect.kernel.Resource.{ Bracket, ExitCase }
import java.lang.Throwable

import scala.Unit

trait FutureInstances {
implicit final val futureBracketInstance: Bracket[Future] =
new FutureMonadError with Bracket[Future] {
final def bracketCase[A, B](acquire: Future[A])(use: A => Future[B])(
release: (A, ExitCase) => Future[Unit]
implicit final val futureMonadCancelInstance
: MonadCancel[Future, Throwable] with MonadCancel.Uncancelable[Future, Throwable] =
new FutureMonadError with MonadCancel[Future, Throwable] with MonadCancel.Uncancelable[Future, Throwable] {

final override def forceR[A, B](fa: Future[A])(fb: Future[B]): Future[B] =
fa.liftToTry.flatMap { resultA =>
resultA.handle(Monitor.catcher)
fb
}

/**
* Special implementation so exceptions in release are cought by the `Monitor`.
*/
final override def bracketCase[A, B](acquire: Future[A])(use: A => Future[B])(
release: (A, Outcome[Future, Throwable, B]) => Future[Unit]
): Future[B] =
acquire
.flatMap(a =>
use(a).liftToTry
.flatMap(result => release(a, tryToExitCase(result)).handle(Monitor.catcher).map(_ => result))
.flatMap(result => release(a, tryToFutureOutcome(result)).handle(Monitor.catcher).map(_ => result))
)
.lowerFromTry
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
package io.catbird.util.effect

import cats.effect.{ Clock, IO, SyncIO }
import com.twitter.util.{ Future, Monitor, Promise }
import cats.effect.Clock
import com.twitter.util.{ Future, Monitor }
import io.catbird.util.{ Rerunnable, RerunnableMonadError }

import java.lang.Throwable
import java.util.concurrent.TimeUnit
import java.lang.System

import cats.Applicative
import cats.effect.kernel.{ MonadCancel, Poll, Sync }
import cats.effect.kernel.Resource.{ Bracket, ExitCase }
import cats.effect.kernel.{ MonadCancel, Outcome, Sync }

import scala.Unit
import scala.concurrent.duration.FiniteDuration
import scala.util.{ Either, Left, Right }

trait RerunnableInstances {
implicit final val rerunnableInstance: Sync[Rerunnable] with Clock[Rerunnable] with Bracket[Rerunnable] =
new RerunnableMonadError with Sync[Rerunnable] with Clock[Rerunnable] with Bracket[Rerunnable] {
implicit final val rerunnableInstance
: Sync[Rerunnable] with Clock[Rerunnable] with MonadCancel.Uncancelable[Rerunnable, Throwable] =
new RerunnableMonadError
with Sync[Rerunnable]
with Clock[Rerunnable]
with MonadCancel.Uncancelable[Rerunnable, Throwable] {

final override def suspend[A](hint: Sync.Type)(thunk: => A): Rerunnable[A] =
Rerunnable(thunk)
Expand All @@ -28,14 +29,23 @@ trait RerunnableInstances {
final override def monotonic: Rerunnable[FiniteDuration] =
Rerunnable(FiniteDuration(System.nanoTime(), TimeUnit.NANOSECONDS))

final override def forceR[A, B](fa: Rerunnable[A])(fb: Rerunnable[B]): Rerunnable[B] =
fa.liftToTry.flatMap { resultA =>
resultA.handle(Monitor.catcher)
fb
}

/**
* Special implementation so exceptions in release are cought by the `Monitor`.
*/
final override def bracketCase[A, B](acquire: Rerunnable[A])(use: A => Rerunnable[B])(
release: (A, ExitCase) => Rerunnable[Unit]
release: (A, Outcome[Rerunnable, Throwable, B]) => Rerunnable[Unit]
): Rerunnable[B] = new Rerunnable[B] {
final def run: Future[B] =
acquire.run.flatMap { a =>
val future = use(a).run
future.transform(result =>
release(a, tryToExitCase(result)).run.handle(Monitor.catcher).flatMap(_ => future)
release(a, tryToRerunnableOutcome(result)).run.handle(Monitor.catcher).flatMap(_ => future)
)
}
}
Expand Down
11 changes: 10 additions & 1 deletion effect/src/main/scala/io/catbird/util/effect/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,20 @@ package object effect extends FutureInstances with RerunnableInstances {
}

/**
* Convert a twitter-util Try to cats-effect Outcome
* Convert a twitter-util Try to cats-effect Outcome for Rerunnable
*/
final def tryToRerunnableOutcome[A](ta: Try[A]): Outcome[Rerunnable, Throwable, A] =
ta match {
case Return(a) => Outcome.Succeeded(Rerunnable.const(a))
case Throw(e) => Outcome.Errored(e)
}

/**
* Convert a twitter-util Try to cats-effect Outcome for Future
*/
final def tryToFutureOutcome[A](ta: Try[A]): Outcome[Future, Throwable, A] =
ta match {
case Return(a) => Outcome.Succeeded(Future.value(a))
case Throw(e) => Outcome.Errored(e)
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package io.catbird.util.effect

import cats.Eq
import cats.laws.discipline.MonadErrorTests
import cats.effect.laws.MonadCancelTests
import cats.instances.all._
import cats.laws.discipline.MonadErrorTests
import cats.laws.discipline.arbitrary._
import com.twitter.conversions.DurationOps._
import com.twitter.util.Future
Expand All @@ -22,4 +23,5 @@ class FutureSuite
futureEqWithFailure(1.seconds)

checkAll("Future[Int]", MonadErrorTests[Future, Throwable].monadError[Int, Int, Int])
checkAll("Future[Int]", MonadCancelTests[Future, Throwable].monadCancel[Int, Int, Int])
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package io.catbird.util.effect

import cats.Eq
import cats.effect.MonadCancel
import cats.effect.kernel.testkit.SyncTypeGenerators
import cats.effect.laws.{ ClockTests, MonadCancelTests, SyncTests }
import cats.effect.testkit.TestContext
import cats.effect.{ IO, Outcome, unsafe }
import cats.effect.Resource.Bracket
import cats.instances.either._
import cats.instances.int._
import cats.instances.tuple._
Expand All @@ -13,24 +11,23 @@ import cats.laws.discipline.MonadErrorTests
import cats.laws.discipline.arbitrary._
import com.twitter.util.{ Await, Monitor, Throw }
import io.catbird.util.{ ArbitraryInstances, EqInstances, Rerunnable }
import org.scalacheck.Prop
import org.scalatest.funsuite.AnyFunSuite
import org.scalatest.prop.Configuration
import org.typelevel.discipline.scalatest.FunSuiteDiscipline
import cats.effect.testkit.SyncTypeGenerators.arbitrarySyncType

class RerunnableSuite
extends AnyFunSuite
with FunSuiteDiscipline
with Configuration
with ArbitraryInstances
with SyncTypeGenerators
with EqInstances
with Runners {

checkAll("Rerunnable[Int]", ClockTests[Rerunnable].clock)
checkAll("Rerunnable[Int]", MonadErrorTests[Rerunnable, Throwable].monadError[Int, Int, Int])
checkAll("Rerunnable[Int]", ClockTests[Rerunnable].clock[Int, Int, Int])
checkAll("Rerunnable[Int]", MonadCancelTests[Rerunnable, Throwable].monadCancel[Int, Int, Int])
checkAll("Rerunnable[Int]", SyncTests[Rerunnable].sync[Int, Int, Int])
//checkAll("Rerunnable[Int]", MonadCancelTests[Rerunnable, Throwable].monadCancel[Int, Int, Int])

test("Exceptions thrown by release are handled by Monitor") {
val useException = new Exception("thrown by use")
Expand All @@ -39,7 +36,7 @@ class RerunnableSuite
var monitoredException: Throwable = null
val monitor = Monitor.mk { case e => monitoredException = e; true; }

val rerunnable = Bracket[Rerunnable]
val rerunnable = MonadCancel[Rerunnable, Throwable]
.bracket(Rerunnable.Unit)(_ => Rerunnable.raiseError(useException))(_ => Rerunnable.raiseError(releaseException))
.liftToTry

Expand Down
2 changes: 2 additions & 0 deletions effect/src/test/scala/io/catbird/util/effect/Runners.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import scala.annotation.implicitNotFound
import scala.concurrent.duration.FiniteDuration
import scala.concurrent.duration._

import scala.language.implicitConversions

/**
* Test helpers mostly taken from the cats-effect IOSpec.
*/
Expand Down

0 comments on commit cd50695

Please sign in to comment.