question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

AsyncHttpClient fails with the exception: received subscription in invalid state

See original GitHub issue

Background

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:closed
  • Created 3 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
rossabakercommented, Jun 18, 2020

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.

1reaction
rossabakercommented, Jun 18, 2020

Yes, @satorg is right. But it will be possible to make an equivalent mistake post-deprecation:

client.run(req).use(IO.pure)

The problem is that when the IO passed fetch or use 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:

// returns an IO[String] on any 2xx response
// raises an UnexpectedStatusError on any non-2xx response
client.expect[String](req)

If you want to specifically handle OK:

client.run(req).use {
  case Ok(resp) => 
    resp.as[String] // return IO[String] if it's 200 OK
  case resp => 
    IO.raiseError(anyThrowableYouLike) // handles other statuses
} // This whole expression returns IO[String]

Let us know if that helps, or if you have any other questions. 😄

Read more comments on GitHub >

github_iconTop 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 >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found