AsyncHttpClient fails with the exception: received subscription in invalid state
See original GitHub issueBackground
I discovered this behaviour while updating the http4s version from 0.21.0
to 0.21.4
.
This seems to have been introduced since the version 0.21.2
.
Setup
Given I have two http4s clients
import org.http4s.client.Client
import org.http4s.client.jetty.JettyClient
import org.http4s.client.asynchttpclient.AsyncHttpClient
import cats.effect.{ContextShift, IO}
import scala.concurrent.ExecutionContext
private implicit val cs: ContextShift[IO] = IO.contextShift(ExecutionContext.global)
val asyncHttpClient: Client[IO] = AsyncHttpClient.allocate[IO]().unsafeRunSync()._1
val jettyHttpClient: Client[IO] = JettyClient.allocate[IO]().unsafeRunSync()._1
And I define a way to extract the body string from the response
implicit class BodyOps(b: EntityBody[IO]) {
def getContent: String = b.through(fs2.text.utf8Decode).compile.string.unsafeRunSync()
}
Then I get the exception java.lang.Error: received subscription in invalid state
with the AsyncHttpClient
but not with the JettyClient
when I try to convert the content of the response stream to a string.
"Async Client" should "work as expected" in {
val request = Request[IO](Method.GET, someUri)
val response = asyncHttpClient.fetch[Response[IO]](request)(IO.pure).unsafeRunSync()
response.status should be (Status.Ok)
response.body.getContent should be ("some response") // Error
}
Full stack trace:
java.lang.Error: received subscription in invalid state [Idle(com.typesafe.netty.HandlerPublisher$ChannelSubscription@cd1b01)]
at fs2.interop.reactivestreams.StreamSubscriber$.$anonfun$fsm$1(StreamSubscriber.scala:103)
at cats.effect.concurrent.Ref$SyncRef.spin$1(Ref.scala:249)
at cats.effect.concurrent.Ref$SyncRef.$anonfun$modify$1(Ref.scala:253)
at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:87)
| => eat cats.effect.internals.IORunLoop$.start(IORunLoop.scala:34)
at cats.effect.IO.unsafeRunAsync(IO.scala:258)
at cats.effect.IO.$anonfun$runAsync$1(IO.scala:178)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at cats.effect.internals.IORunLoop$.step(IORunLoop.scala:190)
at cats.effect.IO.unsafeRunTimed(IO.scala:321)
at cats.effect.IO.unsafeRunSync(IO.scala:240)
at cats.effect.SyncIO.unsafeRunSync(SyncIO.scala:51)
at fs2.interop.reactivestreams.package$Runner.unsafeRunAsync(package.scala:71)
at fs2.interop.reactivestreams.StreamSubscriber.onSubscribe(StreamSubscriber.scala:24)
at com.typesafe.netty.HandlerPublisher.subscribe(HandlerPublisher.java:159)
at org.http4s.client.asynchttpclient.AsyncHttpClient$$anon$1.$anonfun$onStream$2(AsyncHttpClient.scala:87)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:87)
at cats.effect.internals.IORunLoop$.startCancelable(IORunLoop.scala:41)
at cats.effect.internals.IOBracket$BracketStart.run(IOBracket.scala:88)
at cats.effect.internals.Trampoline.cats$effect$internals$Trampoline$$immediateLoop(Trampoline.scala:67)
at cats.effect.internals.Trampoline.startLoop(Trampoline.scala:35)
at cats.effect.internals.TrampolineEC$JVMTrampoline.super$startLoop(TrampolineEC.scala:89)
at cats.effect.internals.TrampolineEC$JVMTrampoline.$anonfun$startLoop$1(TrampolineEC.scala:89)
at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:12)
at scala.concurrent.BlockContext$.withBlockContext(BlockContext.scala:81)
at cats.effect.internals.TrampolineEC$JVMTrampoline.startLoop(TrampolineEC.scala:89)
at cats.effect.internals.Trampoline.execute(Trampoline.scala:43)
at cats.effect.internals.TrampolineEC.execute(TrampolineEC.scala:42)
at cats.effect.internals.IOBracket$BracketStart.apply(IOBracket.scala:69)
at cats.effect.internals.IOBracket$BracketStart.apply(IOBracket.scala:49)
at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:139)
at cats.effect.internals.IORunLoop$.start(IORunLoop.scala:34)
at cats.effect.internals.IOBracket$.$anonfun$apply$1(IOBracket.scala:42)
at cats.effect.internals.IOBracket$.$anonfun$apply$1$adapted(IOBracket.scala:32)
at cats.effect.internals.IORunLoop$RestartCallback.start(IORunLoop.scala:345)
at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:122)
at cats.effect.internals.IORunLoop$.$anonfun$suspendAsync$1(IORunLoop.scala:258)
at cats.effect.internals.IORunLoop$.$anonfun$suspendAsync$1$adapted(IORunLoop.scala:257)
at cats.effect.internals.IORunLoop$RestartCallback.start(IORunLoop.scala:345)
at cats.effect.internals.IORunLoop$.cats$effect$internals$IORunLoop$$loop(IORunLoop.scala:122)
at cats.effect.internals.IORunLoop$.start(IORunLoop.scala:34)
at cats.effect.IO.unsafeRunAsync(IO.scala:258)
at cats.effect.internals.IOPlatform$.unsafeResync(IOPlatform.scala:38)
at cats.effect.IO.unsafeRunTimed(IO.scala:325)
at cats.effect.IO.unsafeRunSync(IO.scala:240)
at example.Http4sClientSpec$BodyOps.getContent(Http4sClientSpec.scala:22)
Issue Analytics
- State:
- Created 3 years ago
- Comments:5 (5 by maintainers)
Top Results From Across the Web
how to make HTTPS calls using AsyncHttpClient?
ssl.SSLPeerUnverifiedException: No peer certificate . Has anyone tried making https call using this library ? initialization of AsyncHttpClient ...
Read more >softwaremill/sttp - Gitter
I have a sttp client request that is failing because the server isn't started after a little bit I get a java.io.IOException: Too...
Read more >My Assets error (Failed to call Unity ID to get auth code)
The console output is: [PackageManager] Error System.InvalidOperationException: Failed to call Unity ID to get auth code. UnityEditor.
Read more >org.asynchttpclient.AsyncHandler.onThrowable java code ...
Invoked when an unexpected exception occurs during the processing of the response. The exception may have been produced by implementation of onXXXReceived ...
Read more >Client Reference — aiohttp 3.8.3 documentation
import aiohttp import asyncio async def fetch(client): async with client.get('http://python.org') as resp: assert resp.status == 200 return await ...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I would say more that it’s undefined behavior: we’ve returned the connection to Jetty, and Jetty’s internal connection pooling can reuse it however it sees fit.
It would be nice if we could make it a compile time error to leak the stream out of
fetch
or.run(...).use
, but the type system isn’t powerful enough for that. If we can’t do that, it would be nice if this usage pattern would consistently and immediately raise an error so you don’t depend on undefined behavior, but we’d have to wrap all the streams, which would cost efficiency. So that’s why we’re in the current situation of undefined behavior, where it might work, but shouldn’t ever be relied upon. It’s a tradeoff.Yes, @satorg is right. But it will be possible to make an equivalent mistake post-deprecation:
The problem is that when the
IO
passedfetch
oruse
is complete, the connection is returned to the client’s pool. You need to be sure to consume everything you need from the response body in that callback.(All snippets below untested, but they’re close…)
A concise way to do this is:
If you want to specifically handle OK:
Let us know if that helps, or if you have any other questions. 😄