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.

Blaze Client keeps half-closed connection after server sends FIN-ACK through an idle connection

See original GitHub issue

This is a problem because both sides can’t release resources allocated to the connection, but also, because blaze will later try and fail to use that connection.

I prepared code that reproduces this behaviour using BlazeServer’s idleTimeout as the mechanism closing the connection on server’s side.

The scenario:

  • make a request
  • wait until server closes the connection (due to 5s idle timeout)
  • make another request

For comparison there’s the same scenario but using AsyncHttpClient. The behaviour of AsyncHttpClient is in my opinion the expected behaviour (It’s also consistent with for example Firefox’s behaviour).

  val server = BlazeServerBuilder[IO](munitExecutionContext)
    .withIdleTimeout(5.second)
    .bindLocal(12345)
    .withHttpApp(Router("/" -> HttpRoutes.of[IO] { case GET -> Root => IO.sleep(1.seconds) >> Ok("") }).orNotFound)
    .resource

  test("Blaze Client keeps half-closed connection after server sends FIN-ACK through an idle connection") {
    (
      server,
      BlazeClientBuilder[IO](munitExecutionContext).resource
    ).tupled.use { case (_, client) =>for {
      _ <- client.expect[String](Uri.fromString(s"http://127.0.0.1:12345").yolo).attemptTap(result => IO(println(result)))
      _ <- IO.sleep(6.seconds)
      _ <- client.expect[String](Uri.fromString(s"http://127.0.0.1:12345").yolo).attemptTap(result => IO(println(result)))
    } yield ()
    }.unsafeRunSync()
  }

  test("AsyncHttpClient closes connection after server sends FIN-ACK through an idle connection") {
    (
      server,
      AsyncHttpClient.resource[IO]()
    ).tupled.use { case (_, client) => for {
      _ <- client.expect[String](Uri.fromString(s"http://127.0.0.1:12345").yolo).attemptTap(result => IO(println(result)))
      _ <- IO.sleep(6.seconds)
      _ <- client.expect[String](Uri.fromString(s"http://127.0.0.1:12345").yolo).attemptTap(result => IO(println(result)))
    } yield ()
    }.unsafeRunSync()
  }

(Full runnable code: https://github.com/RafalSumislawski/http4s/tree/blaze-half-closed-connection-issue)

The code itself just reproduces the behaviour, without checking if it’s correct. I use wireshark to analyse to communication between the client and server.

Blaze client:

image

In packet 1519 server closes the connection. The client ACKs it, but it doesn’t send a FIN, so the connection remains half-closed (CLOSE_WAIT from server’s perspective). Then packet 1521 is clients attempt to use that connection to send another request, to which the server responds with RST finally killing the connection. The client then establishes a new connection and successfully resends the request. Nonetheless precious resources have been wasted on the attempt to send request though closed connection. The retry mechanism was useful in this scenario, but it just covers the problem, not fixes it. And I see some other scenarios where it causes problems.

AsyncHttpClient:

image

After receiving FIN-ACK, the AsyncHttpClient immediately responds with FIN-ACK releasing resource on both ends. When a new request needs to be sent, I understands that it needs to open a new connection.

I realise this may be an issue inside blaze, not http4s, nonetheless I think the perspective of http4s-client user, and the comparison with other implementation is useful for clear definition of the problem and the expected behaviour.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:8 (8 by maintainers)

github_iconTop GitHub Comments

1reaction
RafalSumislawskicommented, May 2, 2021

I’ll try to find the source of the problem.

0reactions
RafalSumislawskicommented, May 8, 2021

I’ve opened a PR fixing this for blaze. I will open a separate issue for ember, so that we don’t keep issues half-open (like ember keeps connections half-open 😉 )

Interesting, so Ember doesn’t even attempt to open a new connection? That’s probably a blind spot in the pooling we should fix.

I actually think that this retry behavior of blaze is more harmful than helpful. Sure it helps in this specific case. But in general, sending the same HTTP request twice is not safe. http4s doesn’t have enough knowledge to say if the request is idempotent or not, or to say if the server started processing the request blaze sent in the first attempt. (see the issue no 2 I described in #4796). Furthermore, blazes retry it unbounded in number of attempts and time. A big no-no for reliability.

Read more comments on GitHub >

github_iconTop Results From Across the Web

K39702017: Connection reset after server sends FIN but client ...
The server and client side connections are reset while waiting for the client to send a FIN, after the server sends a FIN...
Read more >
Why does DefaultHttpClient send data over a half-closed ...
After ~200 seconds of idle time, the server closes the TCP connection with a [FIN]. The Android client responds with an [ACK].
Read more >
Why TCP Connect Termination Need 4-Way-Handshake?
Firstly, from one side of the connection, either from the client or the server the FIN flag will be sent as the request...
Read more >
4.7. The Mysteries of Connection Close - HTTP - O'Reilly
When a connection closes after some request data was sent but before the response is returned, the client cannot be 100% sure how...
Read more >
Connections in FIN_WAIT_2 and Apache - Oracle Help Center
When the connection is idle and the server closes the connection (based on the KeepAliveTimeout), the client is programmed so that the client...
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