Skip to content

Commit

Permalink
fix query cancellation with cancelable
Browse files Browse the repository at this point in the history
  • Loading branch information
TalkingFoxMid committed Aug 12, 2024
1 parent d1f84e6 commit 4b90d8f
Show file tree
Hide file tree
Showing 29 changed files with 302 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ jobs:
run: sbt +update

- name: Start up Postgres
run: docker-compose up -d
run: docker compose up -d

- name: Check Headers
run: sbt '++ ${{ matrix.scala }}' headerCheckAll
Expand Down
3 changes: 2 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ ThisBuild / tlSonatypeUseLegacyHost := false
ThisBuild / githubWorkflowJavaVersions := Seq(JavaSpec.temurin("11"))
ThisBuild / githubWorkflowBuildPreamble ++= Seq(
WorkflowStep.Run(
commands = List("docker-compose up -d"),
commands = List("docker compose up -d"),
name = Some("Start up Postgres")
),
WorkflowStep.Sbt(
Expand Down Expand Up @@ -426,6 +426,7 @@ lazy val hikari = project
libraryDependencies ++= Seq(
// needs to be excluded, otherwise coursier may resolve slf4j-api 2 if > Java 11
"com.zaxxer" % "HikariCP" % hikariVersion exclude ("org.slf4j", "slf4j-api"),
"org.postgresql" % "postgresql" % postgresVersion % "test",
"com.h2database" % "h2" % h2Version % "test",
"org.slf4j" % "slf4j-api" % slf4jVersion,
"org.slf4j" % "slf4j-nop" % slf4jVersion % "test"
Expand Down
22 changes: 12 additions & 10 deletions modules/core/src/main/scala/doobie/hi/connection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package doobie.hi
import cats.Foldable
import cats.data.Ior
import cats.effect.Sync
import cats.effect.kernel.Outcome
import cats.effect.syntax.monadCancel.*
import cats.syntax.all.*
import doobie.enumerated.AutoGeneratedKeys
Expand All @@ -23,15 +24,8 @@ import doobie.util.stream.repeatEvalChunks
import doobie.util.log.{LogEvent, LoggingInfo}
import doobie.util.{Get, Put, Read, Write}
import fs2.Stream
import doobie.hi.{preparedstatement as IHPS}
import doobie.free.{
callablestatement as IFCS,
connection as IFC,
databasemetadata as IFDMD,
preparedstatement as IFPS,
resultset as IFRS,
statement as IFS
}
import doobie.hi.preparedstatement as IHPS
import doobie.free.{callablestatement as IFCS, connection as IFC, databasemetadata as IFDMD, preparedstatement as IFPS, resultset as IFRS, statement as IFS}

import scala.Predef.*
import scala.concurrent.duration.{Duration, FiniteDuration, NANOSECONDS}
Expand Down Expand Up @@ -175,7 +169,15 @@ object connection {
}

createLogged
.bracket(ps => IFC.embed(ps, prepLogged *> execAndProcessLogged))(ps => IFC.embed(ps, IFPS.close))
.flatMap(
ps => WeakAsyncConnectionIO.cancelable(
IFC.embed(ps, prepLogged *> execAndProcessLogged).guaranteeCase {
case Outcome.Canceled() => WeakAsyncConnectionIO.unit
case _ => IFC.embed(ps, IFPS.close)
},
IFC.embed(ps, IFPS.close)
)
)
}

/** Execute a PreparedStatement query and provide rows from the ResultSet in chunks
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) 2013-2020 Rob Norris and Contributors
// This software is licensed under the MIT License (MIT).
// For more information see LICENSE or https://opensource.org/licenses/MIT

package doobie.util

import cats.effect.IO
import doobie.Transactor
import doobie.*
import doobie.implicits.*
import cats.syntax.all.*

import scala.concurrent.duration.DurationInt

class QueryCancellationSuite extends munit.FunSuite {
import cats.effect.unsafe.implicits.global

val xa = Transactor.fromDriverManager[IO](
driver = "org.h2.Driver",
url = "jdbc:h2:mem:queryspec;DB_CLOSE_DELAY=-1",
user = "sa",
password = "",
logHandler = None
)

test("Query cancel") {
val scenario = WeakAsync.liftIO[ConnectionIO].use { elevator =>
for {
_ <- sql"CREATE TABLE IF NOT EXISTS example_table ( id INT)".update.run.transact(xa)
_ <- sql"TRUNCATE TABLE example_table".update.run.transact(xa)
_ <- sql"INSERT INTO example_table (id) VALUES (1)".update.run.transact(xa)
_ <- {
sql"select * from example_table for update".query[Int].unique >> elevator.liftIO(IO.never)
}.transact(xa).start

insertWithLockFiber <- {
for {
_ <- IO.sleep(100.milli)
insertFiber <- sql"UPDATE example_table SET id = 2".update.run.transact(xa).start
_ <- IO.sleep(100.milli)
_ <- insertFiber.cancel
} yield ()
}.start

_ <- IO.race(insertWithLockFiber.join, IO.sleep(5.seconds) >> IO(fail("Cancellation is blocked")))
result <- sql"SELECT * FROM example_table".query[Int].to[List].transact(xa)
} yield assertEquals(result, List(1))
}
scenario.unsafeRunSync()
}
}
3 changes: 3 additions & 0 deletions modules/free/src/main/scala/doobie/WeakAsync.scala
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import scala.concurrent.duration.FiniteDuration
trait WeakAsync[F[_]] extends Sync[F] {
def fromFuture[A](fut: F[Future[A]]): F[A]
def fromFutureCancelable[A](fut: F[(Future[A], F[Unit])]): F[A]
def cancelable[A](fa: F[A], fin: F[Unit]): F[A]
}

object WeakAsync {
Expand All @@ -39,6 +40,8 @@ object WeakAsync {
override def onCancel[A](fa: F[A], fin: F[Unit]): F[A] = F.onCancel(fa, fin)
override def fromFuture[A](fut: F[Future[A]]): F[A] = F.fromFuture(fut)
override def fromFutureCancelable[A](fut: F[(Future[A], F[Unit])]): F[A] = F.fromFutureCancelable(fut)

override def cancelable[A](fa: F[A], fin: F[Unit]): F[A] = F.cancelable(fa, fin)
}

/** Create a natural transformation for lifting an `Async` effect `F` into a `WeakAsync` effect `G`
Expand Down
6 changes: 6 additions & 0 deletions modules/free/src/main/scala/doobie/free/blob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ object blob { module =>
def onCancel[A](fa: BlobIO[A], fin: BlobIO[Unit]): F[A]
def fromFuture[A](fut: BlobIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: BlobIO[(Future[A], BlobIO[Unit])]): F[A]
def cancelable[A](fa: BlobIO[A], fin: BlobIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// Blob
Expand Down Expand Up @@ -119,6 +120,9 @@ object blob { module =>
case class FromFutureCancelable[A](fut: BlobIO[(Future[A], BlobIO[Unit])]) extends BlobOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: BlobIO[A], fin: BlobIO[Unit]) extends BlobOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends BlobOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -181,6 +185,7 @@ object blob { module =>
def onCancel[A](fa: BlobIO[A], fin: BlobIO[Unit]) = FF.liftF[BlobOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: BlobIO[Future[A]]) = FF.liftF[BlobOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: BlobIO[(Future[A], BlobIO[Unit])]) = FF.liftF[BlobOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: BlobIO[A], fin: BlobIO[Unit]) = FF.liftF[BlobOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[BlobOp, Unit](PerformLogging(event))

// Smart constructors for Blob-specific operations.
Expand Down Expand Up @@ -216,6 +221,7 @@ object blob { module =>
override def onCancel[A](fa: BlobIO[A], fin: BlobIO[Unit]): BlobIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: BlobIO[Future[A]]): BlobIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: BlobIO[(Future[A], BlobIO[Unit])]): BlobIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: BlobIO[A], fin: BlobIO[Unit]): BlobIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidBlobIO[A : Monoid]: Monoid[BlobIO[A]] = new Monoid[BlobIO[A]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ object callablestatement { module =>
def onCancel[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]): F[A]
def fromFuture[A](fut: CallableStatementIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: CallableStatementIO[(Future[A], CallableStatementIO[Unit])]): F[A]
def cancelable[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// CallableStatement
Expand Down Expand Up @@ -362,6 +363,9 @@ object callablestatement { module =>
case class FromFutureCancelable[A](fut: CallableStatementIO[(Future[A], CallableStatementIO[Unit])]) extends CallableStatementOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]) extends CallableStatementOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends CallableStatementOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -1090,6 +1094,7 @@ object callablestatement { module =>
def onCancel[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]) = FF.liftF[CallableStatementOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: CallableStatementIO[Future[A]]) = FF.liftF[CallableStatementOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: CallableStatementIO[(Future[A], CallableStatementIO[Unit])]) = FF.liftF[CallableStatementOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]) = FF.liftF[CallableStatementOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[CallableStatementOp, Unit](PerformLogging(event))

// Smart constructors for CallableStatement-specific operations.
Expand Down Expand Up @@ -1347,6 +1352,7 @@ object callablestatement { module =>
override def onCancel[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]): CallableStatementIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: CallableStatementIO[Future[A]]): CallableStatementIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: CallableStatementIO[(Future[A], CallableStatementIO[Unit])]): CallableStatementIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: CallableStatementIO[A], fin: CallableStatementIO[Unit]): CallableStatementIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidCallableStatementIO[A : Monoid]: Monoid[CallableStatementIO[A]] = new Monoid[CallableStatementIO[A]] {
Expand Down
6 changes: 6 additions & 0 deletions modules/free/src/main/scala/doobie/free/clob.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object clob { module =>
def onCancel[A](fa: ClobIO[A], fin: ClobIO[Unit]): F[A]
def fromFuture[A](fut: ClobIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: ClobIO[(Future[A], ClobIO[Unit])]): F[A]
def cancelable[A](fa: ClobIO[A], fin: ClobIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// Clob
Expand Down Expand Up @@ -124,6 +125,9 @@ object clob { module =>
case class FromFutureCancelable[A](fut: ClobIO[(Future[A], ClobIO[Unit])]) extends ClobOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: ClobIO[A], fin: ClobIO[Unit]) extends ClobOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends ClobOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -192,6 +196,7 @@ object clob { module =>
def onCancel[A](fa: ClobIO[A], fin: ClobIO[Unit]) = FF.liftF[ClobOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: ClobIO[Future[A]]) = FF.liftF[ClobOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: ClobIO[(Future[A], ClobIO[Unit])]) = FF.liftF[ClobOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: ClobIO[A], fin: ClobIO[Unit]) = FF.liftF[ClobOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[ClobOp, Unit](PerformLogging(event))

// Smart constructors for Clob-specific operations.
Expand Down Expand Up @@ -229,6 +234,7 @@ object clob { module =>
override def onCancel[A](fa: ClobIO[A], fin: ClobIO[Unit]): ClobIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: ClobIO[Future[A]]): ClobIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: ClobIO[(Future[A], ClobIO[Unit])]): ClobIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: ClobIO[A], fin: ClobIO[Unit]): ClobIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidClobIO[A : Monoid]: Monoid[ClobIO[A]] = new Monoid[ClobIO[A]] {
Expand Down
6 changes: 6 additions & 0 deletions modules/free/src/main/scala/doobie/free/connection.scala
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ object connection { module =>
def onCancel[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]): F[A]
def fromFuture[A](fut: ConnectionIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: ConnectionIO[(Future[A], ConnectionIO[Unit])]): F[A]
def cancelable[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// Connection
Expand Down Expand Up @@ -183,6 +184,9 @@ object connection { module =>
case class FromFutureCancelable[A](fut: ConnectionIO[(Future[A], ConnectionIO[Unit])]) extends ConnectionOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]) extends ConnectionOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends ConnectionOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -392,6 +396,7 @@ object connection { module =>
def onCancel[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]) = FF.liftF[ConnectionOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: ConnectionIO[Future[A]]) = FF.liftF[ConnectionOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: ConnectionIO[(Future[A], ConnectionIO[Unit])]) = FF.liftF[ConnectionOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]) = FF.liftF[ConnectionOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[ConnectionOp, Unit](PerformLogging(event))

// Smart constructors for Connection-specific operations.
Expand Down Expand Up @@ -476,6 +481,7 @@ object connection { module =>
override def onCancel[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]): ConnectionIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: ConnectionIO[Future[A]]): ConnectionIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: ConnectionIO[(Future[A], ConnectionIO[Unit])]): ConnectionIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: ConnectionIO[A], fin: ConnectionIO[Unit]): ConnectionIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidConnectionIO[A : Monoid]: Monoid[ConnectionIO[A]] = new Monoid[ConnectionIO[A]] {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object databasemetadata { module =>
def onCancel[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]): F[A]
def fromFuture[A](fut: DatabaseMetaDataIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: DatabaseMetaDataIO[(Future[A], DatabaseMetaDataIO[Unit])]): F[A]
def cancelable[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// DatabaseMetaData
Expand Down Expand Up @@ -290,6 +291,9 @@ object databasemetadata { module =>
case class FromFutureCancelable[A](fut: DatabaseMetaDataIO[(Future[A], DatabaseMetaDataIO[Unit])]) extends DatabaseMetaDataOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]) extends DatabaseMetaDataOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends DatabaseMetaDataOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -856,6 +860,7 @@ object databasemetadata { module =>
def onCancel[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]) = FF.liftF[DatabaseMetaDataOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: DatabaseMetaDataIO[Future[A]]) = FF.liftF[DatabaseMetaDataOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: DatabaseMetaDataIO[(Future[A], DatabaseMetaDataIO[Unit])]) = FF.liftF[DatabaseMetaDataOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]) = FF.liftF[DatabaseMetaDataOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[DatabaseMetaDataOp, Unit](PerformLogging(event))

// Smart constructors for DatabaseMetaData-specific operations.
Expand Down Expand Up @@ -1059,6 +1064,7 @@ object databasemetadata { module =>
override def onCancel[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]): DatabaseMetaDataIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: DatabaseMetaDataIO[Future[A]]): DatabaseMetaDataIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: DatabaseMetaDataIO[(Future[A], DatabaseMetaDataIO[Unit])]): DatabaseMetaDataIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: DatabaseMetaDataIO[A], fin: DatabaseMetaDataIO[Unit]): DatabaseMetaDataIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidDatabaseMetaDataIO[A : Monoid]: Monoid[DatabaseMetaDataIO[A]] = new Monoid[DatabaseMetaDataIO[A]] {
Expand Down
6 changes: 6 additions & 0 deletions modules/free/src/main/scala/doobie/free/driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ object driver { module =>
def onCancel[A](fa: DriverIO[A], fin: DriverIO[Unit]): F[A]
def fromFuture[A](fut: DriverIO[Future[A]]): F[A]
def fromFutureCancelable[A](fut: DriverIO[(Future[A], DriverIO[Unit])]): F[A]
def cancelable[A](fa: DriverIO[A], fin: DriverIO[Unit]): F[A]
def performLogging(event: LogEvent): F[Unit]

// Driver
Expand Down Expand Up @@ -118,6 +119,9 @@ object driver { module =>
case class FromFutureCancelable[A](fut: DriverIO[(Future[A], DriverIO[Unit])]) extends DriverOp[A] {
def visit[F[_]](v: Visitor[F]) = v.fromFutureCancelable(fut)
}
case class Cancelable[A](fa: DriverIO[A], fin: DriverIO[Unit]) extends DriverOp[A] {
def visit[F[_]](v: Visitor[F]) = v.cancelable(fa, fin)
}
case class PerformLogging(event: LogEvent) extends DriverOp[Unit] {
def visit[F[_]](v: Visitor[F]) = v.performLogging(event)
}
Expand Down Expand Up @@ -168,6 +172,7 @@ object driver { module =>
def onCancel[A](fa: DriverIO[A], fin: DriverIO[Unit]) = FF.liftF[DriverOp, A](OnCancel(fa, fin))
def fromFuture[A](fut: DriverIO[Future[A]]) = FF.liftF[DriverOp, A](FromFuture(fut))
def fromFutureCancelable[A](fut: DriverIO[(Future[A], DriverIO[Unit])]) = FF.liftF[DriverOp, A](FromFutureCancelable(fut))
def cancelable[A](fa: DriverIO[A], fin: DriverIO[Unit]) = FF.liftF[DriverOp, A](Cancelable(fa, fin))
def performLogging(event: LogEvent) = FF.liftF[DriverOp, Unit](PerformLogging(event))

// Smart constructors for Driver-specific operations.
Expand Down Expand Up @@ -199,6 +204,7 @@ object driver { module =>
override def onCancel[A](fa: DriverIO[A], fin: DriverIO[Unit]): DriverIO[A] = module.onCancel(fa, fin)
override def fromFuture[A](fut: DriverIO[Future[A]]): DriverIO[A] = module.fromFuture(fut)
override def fromFutureCancelable[A](fut: DriverIO[(Future[A], DriverIO[Unit])]): DriverIO[A] = module.fromFutureCancelable(fut)
override def cancelable[A](fa: DriverIO[A], fin: DriverIO[Unit]): DriverIO[A] = module.cancelable(fa, fin)
}

implicit def MonoidDriverIO[A : Monoid]: Monoid[DriverIO[A]] = new Monoid[DriverIO[A]] {
Expand Down
Loading

0 comments on commit 4b90d8f

Please sign in to comment.