Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

java.io.IOException: Broken pipe occurring on client calls with empty bodies #515

Closed
ahjohannessen opened this issue Oct 14, 2022 · 9 comments · Fixed by #522
Closed

java.io.IOException: Broken pipe occurring on client calls with empty bodies #515

ahjohannessen opened this issue Oct 14, 2022 · 9 comments · Fixed by #522

Comments

@ahjohannessen
Copy link
Contributor

ahjohannessen commented Oct 14, 2022

Seems like there is a bug in how empty body is handled. I have a repro here. The smithy spec show cases differences in payload. Details are found here.

@Baccata Baccata changed the title body="" vs. body="{}" java.io.IOException: Broken pipe occurring on client calls with empty bodies Oct 14, 2022
@Baccata
Copy link
Contributor

Baccata commented Oct 14, 2022

The fact that the body is empty is normal on requests/responses that transmit all their data through metadata. The fact that the client raises an exception is not.

@daddykotex
Copy link
Contributor

This is interesting to look at. I guess other issues are coming into play here. If I keep the service modification that you've made @ahjohannessen in your repro but adjust the code to this:

package com.example

import smithy4s.hello._
import cats.effect._
import cats.effect.syntax.resource._
import cats.implicits._
import org.http4s.implicits._
import org.http4s.ember.server._
import org.http4s._
import com.comcast.ip4s._
import smithy4s.http4s.SimpleRestJsonBuilder
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.client.Client

object HelloWorldImpl extends HelloWorldService[IO] {

  def hello(
      name: String,
      town: Option[String]
  ): IO[HelloOutput] =
    IO.pure { HelloOutput("hello") }

  def hello2(
      name: String,
      age: Option[Int],
      town: Option[String]
  ): IO[Hello2Output] =
    IO.pure { Hello2Output("hello2") }
}

object Routes {
  private val example: Resource[IO, HttpRoutes[IO]] =
    SimpleRestJsonBuilder.routes(HelloWorldImpl).resource

  private val docs: HttpRoutes[IO] =
    smithy4s.http4s.swagger.docs[IO](HelloWorldService)

  val all: Resource[IO, HttpRoutes[IO]] = example.map(_ <+> docs)
}

abstract class Setup extends IOApp.Simple {
  // client middleware
  val authMiddleware: org.http4s.client.Middleware[IO] = { client =>
    Client { req =>
      client.run(
        req.withHeaders(
          headers.Authorization(Credentials.Token(AuthScheme.Bearer, "TOKEN"))
        )
      )
    }
  }

  val client = EmberClientBuilder.default[IO].build.map { client =>
    SimpleRestJsonBuilder(HelloWorldService)
      .clientResource(
        authMiddleware(client),
        Uri.unsafeFromString("http://127.0.0.1:9000")
      )
  }

  val run = (client, Routes.all).tupled.flatMap { case (client, routes) =>
    for {
      hs <- client
      _ <- EmberServerBuilder
        .default[IO]
        .withPort(port"9000")
        .withHost(host"0.0.0.0")
        .withHttpApp(routes.orNotFound)
        .build
      _ <- clientOp(hs).toResource
    } yield ()
  }.use_

  def clientOp(hs: HelloWorldService[IO]): IO[Unit]
}

object FastServer extends Setup {
  def clientOp(hs: HelloWorldService[IO]): IO[Unit] = IO.unit
}

object Hello2 extends Setup {
  def clientOp(hs: HelloWorldService[IO]): IO[Unit] =
    hs.hello2("hey!").void
}

object Hello extends Setup {
  def clientOp(hs: HelloWorldService[IO]): IO[Unit] =
    hs.hello("hey!").void
}

object Broken extends Setup {
  def clientOp(hs: HelloWorldService[IO]): IO[Unit] =
    hs.hello("hey!") *> hs.hello2("hey 2!").void
}

Ran all 3 (Hello, Hello2 and Broken) with a script:

> ./benchmark.sh 

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
HTTP/1.1 POST http://127.0.0.1:9000/hello/hey! body=""
HTTP/1.1 200 OK body="{"message":"hello"}"
HelloOutput(hello)

real    0m7.283s
user    0m19.039s
sys     0m1.558s
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
HTTP/1.1 POST http://127.0.0.1:9000/hello2/hey! body="{}"
HTTP/1.1 200 OK body="{"message":"hello2"}"
Hello2Output(hello2)

real    *0m35.766s*
user    0m13.703s
sys     0m1.017s
SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
HTTP/1.1 POST http://127.0.0.1:9000/hello/hey! body=""
HTTP/1.1 200 OK body="{"message":"hello"}"
HTTP/1.1 POST http://127.0.0.1:9000/hello2/hey%202! body=""
[error] java.io.IOException: Broken pipe
[error]         at java.base/sun.nio.ch.FileDispatcherImpl.write0(Native Method)
[error]         at java.base/sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:62)
[error]         at java.base/sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:132)
[error]         at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:97)
[error]         at java.base/sun.nio.ch.IOUtil.write(IOUtil.java:60)
[error]         at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finishWrite(UnixAsynchronousSocketChannelImpl.java:602)
[error]         at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.finish(UnixAsynchronousSocketChannelImpl.java:199)
[error]         at java.base/sun.nio.ch.UnixAsynchronousSocketChannelImpl.onEvent(UnixAsynchronousSocketChannelImpl.java:217)
[error]         at java.base/sun.nio.ch.KQueuePort$EventHandlerTask.run(KQueuePort.java:312)
[error]         at java.base/sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:113)
[error]         at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
[error]         at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
[error]         at java.base/java.lang.Thread.run(Thread.java:833)
[error]         at delay @ fs2.io.net.SocketCompanionPlatform$AsyncSocket.$anonfun$write$1(SocketPlatform.scala:137)
[error]         at async @ fs2.io.net.SocketCompanionPlatform$AsyncSocket.go$2(SocketPlatform.scala:131)
[error]         at flatMap @ fs2.io.net.SocketCompanionPlatform$AsyncSocket.go$2(SocketPlatform.scala:141)
[error]         at modify @ org.http4s.ember.server.internal.Shutdown$$anon$1.<init>(Shutdown.scala:83)
[error]         at flatten$extension @ org.http4s.ember.server.internal.Shutdown$$anon$1.<init>(Shutdown.scala:71)
[error]         at apply @ org.http4s.ember.server.EmberServerBuilder.$anonfun$build$2(EmberServerBuilder.scala:177)
[error] stack trace is suppressed; run 'last Compile / runMain' for the full output
[error] (Compile / runMain) java.io.IOException: Broken pipe

real    0m5.969s
user    0m13.843s
sys     0m0.969s

What's interesting is that

  • calling hs.hello alone works
  • calling hs.hello2 alone works but is super slow
  • calling both in succession fail

I'm still looking into it

@daddykotex
Copy link
Contributor

To be honest, it feels like it's an issue outside of smithy4s scope: fs2 or http4s related

Like if the the calls to the client, in succession, consumed (or not) a stream and we end up in an invalid state.

calling hs.hello then hs.hello2 fails, but calling hs.hello2 twice will not fail

@daddykotex
Copy link
Contributor

but it could also be our usage of http4s and fs2 in the client that does this

@ahjohannessen
Copy link
Contributor Author

@daddykotex What happens if you use http2 in ember server?

@daddykotex
Copy link
Contributor

@daddykotex What happens if you use http2 in ember server?

it has no effect unfortunately

@ahjohannessen
Copy link
Contributor Author

@daddykotex It might be related to this http4s/http4s#4935

@daddykotex
Copy link
Contributor

Ok, this is definitely a bug in ember. Switching to blaze fixes the issue. I'm not sure if it's related to the one you referred too but it's good to know anyway.

@daddykotex
Copy link
Contributor

Fixed in the 0.17.0 release

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants