From e9bf16da7d3600b6a7643693a276eb73f7618420 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 9 Nov 2023 17:31:30 +0000 Subject: [PATCH 01/12] Fix leak in fromFutureCancelable --- .../src/main/scala/cats/effect/kernel/Async.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index 06b534f929..f225de21e4 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -222,11 +222,13 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { * Like [[fromFuture]], but is cancelable via the provided finalizer. */ def fromFutureCancelable[A](futCancel: F[(Future[A], F[Unit])]): F[A] = - flatMap(futCancel) { - case (fut, fin) => - flatMap(executionContext) { implicit ec => - async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin))) - } + async[A] { cb => + flatMap(futCancel) { + case (fut, fin) => + flatMap(executionContext) { implicit ec => + as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin)) + } + } } /** From 4d0a3c9e64d03a42ba37688515710d3aa19991c1 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 9 Nov 2023 17:35:13 +0000 Subject: [PATCH 02/12] Attempt to fix leak in fromFuture as well --- kernel/shared/src/main/scala/cats/effect/kernel/Async.scala | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index f225de21e4..1d22158cf4 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -212,9 +212,9 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { * [[fromFutureCancelable]] for a cancelable version */ def fromFuture[A](fut: F[Future[A]]): F[A] = - flatMap(fut) { f => - flatMap(executionContext) { implicit ec => - async_[A](cb => f.onComplete(t => cb(t.toEither))) + async_[A] { cb => + flatMap(fut) { f => + map(executionContext) { implicit ec => f.onComplete(t => cb(t.toEither)) } } } From 76b8026ea5cb3e2288d8f62ed762a680983f2922 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Fri, 17 Nov 2023 12:17:54 +0000 Subject: [PATCH 03/12] Avoid leaking future in fromFutureCancelable and reduce opportunity for leaks in fromFuture --- .../src/main/scala/cats/effect/kernel/Async.scala | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index 1d22158cf4..8853dc7a7b 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -212,22 +212,18 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { * [[fromFutureCancelable]] for a cancelable version */ def fromFuture[A](fut: F[Future[A]]): F[A] = - async_[A] { cb => - flatMap(fut) { f => - map(executionContext) { implicit ec => f.onComplete(t => cb(t.toEither)) } - } + flatMap(executionContext) { implicit ec => + flatMap(fut) { f => async_[A](cb => f.onComplete(t => cb(t.toEither))) } } /** * Like [[fromFuture]], but is cancelable via the provided finalizer. */ def fromFutureCancelable[A](futCancel: F[(Future[A], F[Unit])]): F[A] = - async[A] { cb => - flatMap(futCancel) { + flatMap(executionContext) { implicit ec => + flatMap(uncancelable(_(futCancel))) { case (fut, fin) => - flatMap(executionContext) { implicit ec => - as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin)) - } + async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin))) } } From 6b25b6d00b6f515799aecc2eef5a5a7b81e55e36 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 22 Nov 2023 12:38:09 +0000 Subject: [PATCH 04/12] Fix leaks in Async#fromFuture plus tests --- .../main/scala/cats/effect/kernel/Async.scala | 4 +- .../scala/cats/effect/kernel/AsyncSpec.scala | 37 ++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index 8853dc7a7b..4ee2197188 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -213,7 +213,9 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { */ def fromFuture[A](fut: F[Future[A]]): F[A] = flatMap(executionContext) { implicit ec => - flatMap(fut) { f => async_[A](cb => f.onComplete(t => cb(t.toEither))) } + uncancelable { poll => + flatMap(poll(fut)) { f => async_[A](cb => f.onComplete(t => cb(t.toEither))) } + } } /** diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 43063b6196..67dbed814b 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -17,17 +17,21 @@ package cats.effect package kernel +import cats.syntax.all._ import cats.{Eq, Order, StackSafeMonad} import cats.arrow.FunctionK import cats.effect.laws.AsyncTests import cats.laws.discipline.arbitrary._ +import cats.effect.std.Random + import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary import org.typelevel.discipline.specs2.mutable.Discipline -import scala.concurrent.ExecutionContext +import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration._ +import java.util.concurrent.atomic.AtomicBoolean class AsyncSpec extends BaseSpec with Discipline { @@ -43,6 +47,37 @@ class AsyncSpec extends BaseSpec with Discipline { ) /*(Parameters(seed = Some(Seed.fromBase64("ZxDXpm7_3Pdkl-Fvt8M90Cxfam9wKuzcifQ1QsIJxND=").get)))*/ } + "fromFuture" should { + "not leak the future on cancelation" in real { + import scala.concurrent.ExecutionContext.Implicits.global + + implicit val rand: Random[IO] = Random.javaUtilConcurrentThreadLocalRandom[IO] + + val jitter = Random[IO].betweenInt(1, 5).flatMap(n => IO.sleep(n.micros)) + + val run = IO(new AtomicBoolean(false) -> new AtomicBoolean(false)).flatMap { + case (started, finished) => + val future = IO { + Future { + started.set(true) + Thread.sleep(20) + finished.set(true) + } + } + + (jitter >> IO.fromFuture(future)).race(jitter).map { _ => + val wasStarted = started.get + val wasFinished = finished.get + + wasStarted mustEqual wasFinished + } + + } + + List.fill(100)(run).sequence + } + } + final class AsyncIO[A](val io: IO[A]) implicit def asyncForAsyncIO: Async[AsyncIO] = new Async[AsyncIO] From 22effa28edf4b35923a9dc73eb03c8a7e3ce3ddc Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Wed, 22 Nov 2023 14:35:36 +0000 Subject: [PATCH 05/12] Rewrite fromFuture test using TestControl ala existing fromCompletableFuture tests --- .../scala/cats/effect/kernel/AsyncSpec.scala | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 67dbed814b..2fa78bea72 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -22,6 +22,8 @@ import cats.{Eq, Order, StackSafeMonad} import cats.arrow.FunctionK import cats.effect.laws.AsyncTests import cats.laws.discipline.arbitrary._ +import cats.effect.testkit.TestControl +import cats.effect.unsafe.IORuntimeConfig import cats.effect.std.Random @@ -29,7 +31,7 @@ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary import org.typelevel.discipline.specs2.mutable.Discipline -import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.{ExecutionContext, Future, Promise} import scala.concurrent.duration._ import java.util.concurrent.atomic.AtomicBoolean @@ -78,6 +80,33 @@ class AsyncSpec extends BaseSpec with Discipline { } } + "fromFuture" should { + "backpressure on cancelation" in real { + // a non-cancelable, never-completing Future + def mkf() = Promise[Unit]().future + + def go = for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromFuture { + IO { + started.set(true) + mkf() + } + }.start + _ <- IO.cede.whileM_(IO(!started.get)) + _ <- fiber.cancel + } yield () + + TestControl + .executeEmbed(go, IORuntimeConfig(1, 2)) + .as(false) + .recover { case _: TestControl.NonTerminationException => true } + .replicateA(1000) + .map(_.forall(identity(_))) + } + + } + final class AsyncIO[A](val io: IO[A]) implicit def asyncForAsyncIO: Async[AsyncIO] = new Async[AsyncIO] From 361ca8f7090402bc3cc819a148f3ff6a02bb3d39 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 12:43:51 +0000 Subject: [PATCH 06/12] Fix Async#fromFutureCancelable --- .../main/scala/cats/effect/kernel/Async.scala | 8 +-- .../scala/cats/effect/kernel/AsyncSpec.scala | 50 +++++++++---------- 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index 4ee2197188..dc09cca06b 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -223,9 +223,11 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { */ def fromFutureCancelable[A](futCancel: F[(Future[A], F[Unit])]): F[A] = flatMap(executionContext) { implicit ec => - flatMap(uncancelable(_(futCancel))) { - case (fut, fin) => - async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin))) + uncancelable { poll => + flatMap(poll(futCancel)) { + case (fut, fin) => + async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin))) + } } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 2fa78bea72..c141e4cdea 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -50,48 +50,44 @@ class AsyncSpec extends BaseSpec with Discipline { } "fromFuture" should { - "not leak the future on cancelation" in real { - import scala.concurrent.ExecutionContext.Implicits.global - - implicit val rand: Random[IO] = Random.javaUtilConcurrentThreadLocalRandom[IO] - - val jitter = Random[IO].betweenInt(1, 5).flatMap(n => IO.sleep(n.micros)) - - val run = IO(new AtomicBoolean(false) -> new AtomicBoolean(false)).flatMap { - case (started, finished) => - val future = IO { - Future { - started.set(true) - Thread.sleep(20) - finished.set(true) - } - } - - (jitter >> IO.fromFuture(future)).race(jitter).map { _ => - val wasStarted = started.get - val wasFinished = finished.get + "backpressure on cancelation" in real { + // a non-cancelable, never-completing Future + def mkf() = Promise[Unit]().future - wasStarted mustEqual wasFinished + def go = for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromFuture { + IO { + started.set(true) + mkf() } + }.start + _ <- IO.cede.whileM_(IO(!started.get)) + _ <- fiber.cancel + } yield () - } - - List.fill(100)(run).sequence + TestControl + .executeEmbed(go, IORuntimeConfig(1, 2)) + .as(false) + .recover { case _: TestControl.NonTerminationException => true } + .replicateA(1000) + .map(_.forall(identity(_))) } + } - "fromFuture" should { + "fromFutureCancelable" should { "backpressure on cancelation" in real { // a non-cancelable, never-completing Future def mkf() = Promise[Unit]().future def go = for { started <- IO(new AtomicBoolean) - fiber <- IO.fromFuture { + fiber <- IO.fromFutureCancelable { IO { started.set(true) mkf() - } + }.map(f => f -> IO.never) }.start _ <- IO.cede.whileM_(IO(!started.get)) _ <- fiber.cancel From a9fb7b38550e0bb3b05bd6a0b81da78d88b16207 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 12:44:45 +0000 Subject: [PATCH 07/12] scalafixAll --- .../src/test/scala/cats/effect/kernel/AsyncSpec.scala | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index c141e4cdea..ac790d89e1 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -17,22 +17,20 @@ package cats.effect package kernel -import cats.syntax.all._ import cats.{Eq, Order, StackSafeMonad} import cats.arrow.FunctionK import cats.effect.laws.AsyncTests -import cats.laws.discipline.arbitrary._ import cats.effect.testkit.TestControl import cats.effect.unsafe.IORuntimeConfig - -import cats.effect.std.Random +import cats.laws.discipline.arbitrary._ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary import org.typelevel.discipline.specs2.mutable.Discipline -import scala.concurrent.{ExecutionContext, Future, Promise} +import scala.concurrent.{ExecutionContext, Promise} import scala.concurrent.duration._ + import java.util.concurrent.atomic.AtomicBoolean class AsyncSpec extends BaseSpec with Discipline { From 73eb17b7b659a16ab1a8caa8ec9a9ca34201c5de Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 14:48:34 +0000 Subject: [PATCH 08/12] Make async node cancelable in Async#fromFutureCancelable Question: is this equivalent to making the whole thing cancelable (bad) --- .../main/scala/cats/effect/kernel/Async.scala | 2 +- .../scala/cats/effect/kernel/AsyncSpec.scala | 23 ++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index dc09cca06b..f1818368f9 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -226,7 +226,7 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { uncancelable { poll => flatMap(poll(futCancel)) { case (fut, fin) => - async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin))) + poll(async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin)))) } } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index ac790d89e1..797f8d33d3 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -75,11 +75,32 @@ class AsyncSpec extends BaseSpec with Discipline { } "fromFutureCancelable" should { + + "cancel on fiber cancelation" in real { + val smallDelay: IO[Unit] = IO.sleep(1.second) + def mkf() = Promise[Unit]().future + val canceled = new AtomicBoolean(false) + + for { + started <- IO(new AtomicBoolean) + fiber <- IO.fromFutureCancelable { + IO { + started.set(true) + mkf() + }.map(f => f -> IO(canceled.set(true))) + }.start + _ <- smallDelay + _ <- fiber.cancel + res <- IO(canceled.get() mustEqual true) + } yield res + + } + "backpressure on cancelation" in real { // a non-cancelable, never-completing Future def mkf() = Promise[Unit]().future - def go = for { + val go = for { started <- IO(new AtomicBoolean) fiber <- IO.fromFutureCancelable { IO { From 38a29199b027d527681a6848074c3b143c00b247 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 14:58:28 +0000 Subject: [PATCH 09/12] Fix new cancelation leak --- .../src/main/scala/cats/effect/kernel/Async.scala | 4 +++- .../test/scala/cats/effect/kernel/AsyncSpec.scala | 15 +++++++-------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala index f1818368f9..455fe08636 100644 --- a/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala +++ b/kernel/shared/src/main/scala/cats/effect/kernel/Async.scala @@ -226,7 +226,9 @@ trait Async[F[_]] extends AsyncPlatform[F] with Sync[F] with Temporal[F] { uncancelable { poll => flatMap(poll(futCancel)) { case (fut, fin) => - poll(async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(fin)))) + onCancel( + poll(async[A](cb => as(delay(fut.onComplete(t => cb(t.toEither))), Some(unit)))), + fin) } } } diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 797f8d33d3..266d270c51 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -17,6 +17,7 @@ package cats.effect package kernel +import cats.syntax.all._ import cats.{Eq, Order, StackSafeMonad} import cats.arrow.FunctionK import cats.effect.laws.AsyncTests @@ -77,23 +78,21 @@ class AsyncSpec extends BaseSpec with Discipline { "fromFutureCancelable" should { "cancel on fiber cancelation" in real { - val smallDelay: IO[Unit] = IO.sleep(1.second) + val smallDelay: IO[Unit] = IO.sleep(10.millis) def mkf() = Promise[Unit]().future - val canceled = new AtomicBoolean(false) - for { - started <- IO(new AtomicBoolean) + val run = for { + canceled <- IO(new AtomicBoolean) fiber <- IO.fromFutureCancelable { - IO { - started.set(true) - mkf() - }.map(f => f -> IO(canceled.set(true))) + IO(mkf()).map(f => f -> IO(canceled.set(true))) }.start _ <- smallDelay _ <- fiber.cancel res <- IO(canceled.get() mustEqual true) } yield res + List.fill(100)(run).sequence + } "backpressure on cancelation" in real { From 36f1d8d45062fe080c0884b911ad56ad412fbf1f Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 14:59:23 +0000 Subject: [PATCH 10/12] scalafixAll --- tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 266d270c51..f702e1b920 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -17,13 +17,13 @@ package cats.effect package kernel -import cats.syntax.all._ import cats.{Eq, Order, StackSafeMonad} import cats.arrow.FunctionK import cats.effect.laws.AsyncTests import cats.effect.testkit.TestControl import cats.effect.unsafe.IORuntimeConfig import cats.laws.discipline.arbitrary._ +import cats.syntax.all._ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary From 72295470fc67319c8c7b24341fd3596646b888d8 Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 16:44:57 +0000 Subject: [PATCH 11/12] Turns out bug is reproducible with TestControl --- .../shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index f702e1b920..5bb54a8aae 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -81,7 +81,7 @@ class AsyncSpec extends BaseSpec with Discipline { val smallDelay: IO[Unit] = IO.sleep(10.millis) def mkf() = Promise[Unit]().future - val run = for { + val go = for { canceled <- IO(new AtomicBoolean) fiber <- IO.fromFutureCancelable { IO(mkf()).map(f => f -> IO(canceled.set(true))) @@ -91,7 +91,7 @@ class AsyncSpec extends BaseSpec with Discipline { res <- IO(canceled.get() mustEqual true) } yield res - List.fill(100)(run).sequence + TestControl.executeEmbed(go, IORuntimeConfig(1, 2)).replicateA(1000) } From 77c404ac15d8bdeeb1df9329ee6d766b6a4dd3ff Mon Sep 17 00:00:00 2001 From: Tim Spence Date: Thu, 23 Nov 2023 17:05:41 +0000 Subject: [PATCH 12/12] One day I will remember to run scalafix --- tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala index 5bb54a8aae..84fb985e9d 100644 --- a/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala +++ b/tests/shared/src/test/scala/cats/effect/kernel/AsyncSpec.scala @@ -23,7 +23,6 @@ import cats.effect.laws.AsyncTests import cats.effect.testkit.TestControl import cats.effect.unsafe.IORuntimeConfig import cats.laws.discipline.arbitrary._ -import cats.syntax.all._ import org.scalacheck.{Arbitrary, Cogen, Prop} import org.scalacheck.Arbitrary.arbitrary