Repository: http4s/http4s
Pull request #6747 (2022-10-12 23:47:51)

Update to Cats Effect 3.4.0-RC2
Cats Effect 3.4.0-RC2 includes significant changes to `Queue` semantics which are used in Ember and especially Ember H2.

Do we want to publish our own RC candidate against this?


Patch:
diff --git a/client/js/src/main/scala/org/http4s/client/NodeJSClientBuilder.scala b/client/js/src/main/scala/org/http4s/client/NodeJSClientBuilder.scala
index b6bd823f08..871b95bffe 100644
--- a/client/js/src/main/scala/org/http4s/client/NodeJSClientBuilder.scala
+++ b/client/js/src/main/scala/org/http4s/client/NodeJSClientBuilder.scala
@@ -19,7 +19,6 @@ package client
 
 import cats.effect.kernel.Async
 import cats.effect.kernel.Resource
-import cats.effect.std.Dispatcher
 import cats.syntax.all._
 import org.http4s.internal.BackendBuilder
 import org.http4s.nodejs.ClientRequest
@@ -49,8 +48,8 @@ private[client] sealed abstract class NodeJSClientBuilder[F[_]](implicit protect
     * The shutdown of this client is a no-op. $WHYNOSHUTDOWN
     */
   def create: Client[F] = Client { (req: Request[F]) =>
-    Dispatcher[F].flatMap { dispatcher =>
-      Resource.make(F.delay(new AbortController))(ctr => F.delay(ctr.abort())).evalMap { abort =>
+    Resource.make(F.delay(new AbortController))(ctr => F.delay(ctr.abort())).evalMap { abort =>
+      F.async[IncomingMessage] { cb =>
         val options = new RequestOptions {
           method = req.method.renderString
           protocol = req.uri.scheme.map(_.value + ":").orUndefined
@@ -60,19 +59,16 @@ private[client] sealed abstract class NodeJSClientBuilder[F[_]](implicit protect
           signal = abort.signal
         }
 
-        for {
-          incomingMessage <- F.deferred[IncomingMessage]
-          cb = (msg: IncomingMessage) =>
-            dispatcher.unsafeRunAndForget(incomingMessage.complete(msg))
-          clientRequest <-
-            if (req.uri.scheme.contains(Uri.Scheme.https))
-              F.delay(httpsRequest(options, cb))
-            else
-              F.delay(httpRequest(options, cb))
-          _ <- clientRequest.writeRequest(req)
-          response <- incomingMessage.get.flatMap(_.toResponse)
-        } yield response
-      }
+        val clientRequest =
+          if (req.uri.scheme.contains(Uri.Scheme.https))
+            F.delay(httpsRequest(options, msg => cb(Right(msg))))
+          else
+            F.delay(httpRequest(options, msg => cb(Right(msg))))
+
+        clientRequest
+          .flatMap(_.writeRequest(req))
+          .as(Some(F.unit)) // cancelation guaranteed by abort controller
+      }.flatMap(_.toResponse)
     }
   }
 
diff --git a/project/Http4sPlugin.scala b/project/Http4sPlugin.scala
index 094242750b..400d52c7d5 100644
--- a/project/Http4sPlugin.scala
+++ b/project/Http4sPlugin.scala
@@ -100,7 +100,7 @@ object Http4sPlugin extends AutoPlugin {
     val blaze = "0.15.3"
     val caseInsensitive = "1.3.0"
     val cats = "2.8.0"
-    val catsEffect = "3.3.14"
+    val catsEffect = "3.4.0-RC2"
     val catsParse = "0.3.8"
     val circe = "0.14.3"
     val crypto = "0.2.4"
diff --git a/server/jvm/src/main/scala/org/http4s/server/middleware/CSRF.scala b/server/jvm/src/main/scala/org/http4s/server/middleware/CSRF.scala
index 913287f813..4de550bc5e 100644
--- a/server/jvm/src/main/scala/org/http4s/server/middleware/CSRF.scala
+++ b/server/jvm/src/main/scala/org/http4s/server/middleware/CSRF.scala
@@ -24,7 +24,7 @@ import cats.data.Kleisli
 import cats.effect.Concurrent
 import cats.effect.Sync
 import cats.effect.SyncIO
-import cats.effect.std.Random
+import cats.effect.std.SecureRandom
 import cats.syntax.all._
 import cats.~>
 import org.http4s.Uri.Scheme
@@ -492,8 +492,8 @@ object CSRF {
     * SecureRandom use via jvm flags.
     */
   private val InitialSeedArraySize: Int = 20
-  private val CachedRandom: Random[SyncIO] =
-    Random
+  private val CachedRandom: SecureRandom[SyncIO] =
+    SecureRandom
       .javaSecuritySecureRandom[SyncIO]
       .flatTap(_.nextBytes(InitialSeedArraySize))
       .unsafeRunSync()
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/authentication/Nonce.scala b/server/shared/src/main/scala/org/http4s/server/middleware/authentication/Nonce.scala
index 76673d2b97..dca5e7d35b 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/authentication/Nonce.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/authentication/Nonce.scala
@@ -17,7 +17,7 @@
 package org.http4s.server.middleware.authentication
 
 import cats.effect.SyncIO
-import cats.effect.std.Random
+import cats.effect.std.SecureRandom
 
 import java.util.Date
 
@@ -26,7 +26,7 @@ private[authentication] class Nonce(val created: Date, var nc: Int, val data: St
 
 @deprecated("Untracked side effects. Use NonceF.", "0.23.11")
 private[authentication] object Nonce {
-  val random: Random[SyncIO] = Random.javaSecuritySecureRandom[SyncIO].unsafeRunSync()
+  val random: SecureRandom[SyncIO] = SecureRandom.javaSecuritySecureRandom[SyncIO].unsafeRunSync()
 
   def gen(bits: Int): Nonce =
     new Nonce(new Date(), 0, NonceF.getRandomData[SyncIO](random, bits).unsafeRunSync())
diff --git a/server/shared/src/main/scala/org/http4s/server/middleware/authentication/NonceKeeperF.scala b/server/shared/src/main/scala/org/http4s/server/middleware/authentication/NonceKeeperF.scala
index 5fb1bdd914..3318d8b6b2 100644
--- a/server/shared/src/main/scala/org/http4s/server/middleware/authentication/NonceKeeperF.scala
+++ b/server/shared/src/main/scala/org/http4s/server/middleware/authentication/NonceKeeperF.scala
@@ -18,7 +18,7 @@ package org.http4s.server.middleware.authentication
 
 import cats.effect.Async
 import cats.effect.Ref
-import cats.effect.std.Random
+import cats.effect.std.SecureRandom
 import cats.effect.std.Semaphore
 import cats.syntax.all._
 
@@ -38,7 +38,7 @@ private[authentication] object NonceKeeperF {
     current <- F.monotonic
     lastCleanupMillis <- Ref[F].of(current)
     nonces = new LinkedHashMap[String, NonceF[F]]
-    random <- Random.javaSecuritySecureRandom[F]
+    random <- SecureRandom.javaSecuritySecureRandom[F]
   } yield new NonceKeeperF(
     staleTimeout,
     nonceCleanupInterval,
@@ -64,7 +64,7 @@ private[authentication] class NonceKeeperF[F[_]](
     semaphore: Semaphore[F],
     lastCleanupMillis: Ref[F, FiniteDuration],
     nonces: LinkedHashMap[String, NonceF[F]],
-    random: Random[F],
+    random: SecureRandom[F],
 )(implicit F: Async[F]) {
   require(bits > 0, "Please supply a positive integer for bits.")
 


Test patch:
diff --git a/client-testkit/js/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala b/client-testkit/js/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
index c7610d62b2..11f5f63210 100644
--- a/client-testkit/js/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
+++ b/client-testkit/js/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
@@ -62,7 +62,7 @@ private[http4s] object ServerScaffold {
   ): Resource[F, ServerScaffold[F]] = {
     require(num == 1 && !secure)
     val app = routes.orNotFound
-    Dispatcher[F].flatMap { dispatcher =>
+    Dispatcher.parallel[F].flatMap { dispatcher =>
       Resource
         .make {
           F.delay(
diff --git a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
index 2a9864a05c..c68a981411 100644
--- a/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
+++ b/client-testkit/jvm/src/main/scala/org/http4s/client/testkit/scaffold/ServerScaffold.scala
@@ -43,7 +43,7 @@ private[http4s] object ServerScaffold {
       F: Async[F]
   ): Resource[F, ServerScaffold[F]] =
     for {
-      dispatcher <- Dispatcher[F]
+      dispatcher <- Dispatcher.parallel[F]
       scaffold <- apply(num, secure, RoutesToNettyAdapter[F](routes, dispatcher))
     } yield scaffold
 
@@ -58,7 +58,7 @@ private[http4s] object ServerScaffold {
       F: Async[F]
   ): Resource[F, ServerScaffold[F]] =
     for {
-      dispatcher <- Dispatcher[F]
+      dispatcher <- Dispatcher.parallel[F]
       maybeSsl <-
         if (secure) Resource.eval[F, SSLContext](makeSslContext[F]).map(Some(_))
         else Resource.pure[F, Option[SSLContext]](None)
diff --git a/server/shared/src/test/scala/org/http4s/server/middleware/authentication/NonceFSpec.scala b/server/shared/src/test/scala/org/http4s/server/middleware/authentication/NonceFSpec.scala
index 1f3fcce2d3..ab6a10eeb3 100644
--- a/server/shared/src/test/scala/org/http4s/server/middleware/authentication/NonceFSpec.scala
+++ b/server/shared/src/test/scala/org/http4s/server/middleware/authentication/NonceFSpec.scala
@@ -18,13 +18,13 @@ package org.http4s
 package server.middleware.authentication
 
 import cats.effect.IO
-import cats.effect.std.Random
+import cats.effect.std.SecureRandom
 import org.scalacheck.Gen
 import org.scalacheck.effect.PropF._
 
 class NonceFSpec extends Http4sSuite {
   test("nonce in expected range") {
-    Random.javaSecuritySecureRandom[IO].map { random =>
+    SecureRandom.javaSecuritySecureRandom[IO].map { random =>
       forAllF(Gen.chooseNum(1, 240)) { (n: Int) =>
         NonceF
           .gen(random, n)
@@ -36,7 +36,7 @@ class NonceFSpec extends Http4sSuite {
 
   test("nonce has correct alphabet") {
     val alphabet = "0123456789abcdef".toSet
-    Random.javaSecuritySecureRandom[IO].map { random =>
+    SecureRandom.javaSecuritySecureRandom[IO].map { random =>
       forAllF { (_: Unit) =>
         NonceF.gen(random, 160).map(nonce => assert(nonce.data.forall(alphabet), nonce.data))
       }
diff --git a/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala b/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
index be07923f1b..b7a3f9649f 100644
--- a/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
+++ b/tests/shared/src/main/scala/org/http4s/testing/DispatcherIOFixture.scala
@@ -23,6 +23,6 @@ import munit.CatsEffectSuite
 
 trait DispatcherIOFixture { this: CatsEffectSuite =>
 
-  def dispatcher: SyncIO[FunFixture[Dispatcher[IO]]] = ResourceFunFixture(Dispatcher[IO])
+  def dispatcher: SyncIO[FunFixture[Dispatcher[IO]]] = ResourceFunFixture(Dispatcher.parallel[IO])
 
 }
diff --git a/tests/shared/src/test/scala/org/http4s/multipart/MultipartSuite.scala b/tests/shared/src/test/scala/org/http4s/multipart/MultipartSuite.scala
index 4391d93fe8..6f9b9cc868 100644
--- a/tests/shared/src/test/scala/org/http4s/multipart/MultipartSuite.scala
+++ b/tests/shared/src/test/scala/org/http4s/multipart/MultipartSuite.scala
@@ -33,7 +33,13 @@ class MultipartSuite extends Http4sSuite {
   private val url = uri"https://example.com/path/to/some/where"
 
   private val multiparts =
-    Random.scalaUtilRandom[IO].map(Multiparts.fromRandom[IO]).syncStep.unsafeRunSync().toOption.get
+    Random
+      .scalaUtilRandom[IO]
+      .map(Multiparts.fromRandom[IO])
+      .syncStep(Int.MaxValue)
+      .unsafeRunSync()
+      .toOption
+      .get
 
   def eqPartIO(a: Part[IO], b: Part[IO]): IO[Boolean] =
     for {
diff --git a/tests/shared/src/test/scala/org/http4s/multipart/MultipartsSuite.scala b/tests/shared/src/test/scala/org/http4s/multipart/MultipartsSuite.scala
index 2d88f5b702..d47a24c0f3 100644
--- a/tests/shared/src/test/scala/org/http4s/multipart/MultipartsSuite.scala
+++ b/tests/shared/src/test/scala/org/http4s/multipart/MultipartsSuite.scala
@@ -23,7 +23,13 @@ import org.scalacheck.effect.PropF._
 
 class MultipartsSuite extends Http4sSuite {
   private val multiparts =
-    Random.scalaUtilRandom[IO].map(Multiparts.fromRandom[IO]).syncStep.unsafeRunSync().toOption.get
+    Random
+      .scalaUtilRandom[IO]
+      .map(Multiparts.fromRandom[IO])
+      .syncStep(Int.MaxValue)
+      .unsafeRunSync()
+      .toOption
+      .get
   private val alphabet =
     Set('A' to 'Z': _*) ++ Set('a' to 'z': _*) ++ Set('0' to '9': _*) ++ Set('_', '-')