higherkindness/mu-scala

gRPC server does not shutdown after `sbt run`

cb372 opened this issue · 4 comments

cb372 commented

If you start a server using sbt run (as we often do in tutorials) and then kill it with Ctrl-C, it keeps running. If you try to run it again, it fails to startup because the port is in use.

When you shutdown sbt you get an exception:

[error] (run-main-0) java.lang.InterruptedException
[error] java.lang.InterruptedException
[error]         at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1040)
[error]         at java.base/java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1345)
[error]         at cats.effect.internals.IOPlatform$.$anonfun$unsafeResync$2(IOPlatform.scala:51)
[error]         at scala.runtime.java8.JFunction0$mcV$sp.apply(JFunction0$mcV$sp.java:23)
[error]         at scala.concurrent.BlockContext$DefaultBlockContext$.blockOn(BlockContext.scala:57)
[error]         at scala.concurrent.package$.blocking(package.scala:146)
[error]         at cats.effect.internals.IOPlatform$.unsafeResync(IOPlatform.scala:51)
[error]         at cats.effect.IO.unsafeRunTimed(IO.scala:325)
[error]         at cats.effect.IO.unsafeRunSync(IO.scala:240)
[error]         at cats.effect.internals.IOAppPlatform$.main(IOAppPlatform.scala:25)
[error]         at cats.effect.IOApp.main(IOApp.scala:67)
[error]         at cats.effect.IOApp.main$(IOApp.scala:66)
[error]         at com.example.Server$.main(Server.scala:7)
[error]         at com.example.Server.main(Server.scala)
[error]         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[error]         at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[error]         at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[error]         at java.base/java.lang.reflect.Method.invoke(Method.java:567)
cb372 commented

Confirmed that this is NOT fixed by #830. It's still happening.

Also when we use IOApp?

cb372 commented

Yes. This is the code I tested with:

package com.example

import cats.effect._
import com.example.hello.Greeter
import higherkindness.mu.rpc.server._

object Server extends IOApp {

  implicit val greeter: Greeter[IO] = new HappyGreeter[IO]

  def run(args: List[String]): IO[ExitCode] = for {
    serviceDef <- Greeter.bindService[IO]
    server     <- GrpcServer.default[IO](12345, List(AddService(serviceDef)))
    _          <- GrpcServer.server[IO](server)
  } yield ExitCode.Success

}
cb372 commented

I investigated this, and it appears it's nothing to do with Mu - it's the interaction between sbt and IOApp.

Here is a minimal reproduction of the problem, and a nice answer from Daniel Spiewak about what's going on.

To summarise: when you press Ctrl-C in sbt while the run task is running, sbt interrupts the IOApp's main thread. But IOApp does not treat that as cancellation, so it keeps running as if nothing happened.

Actually it's not only IOApp that suffers from this problem. Here's an example with akka-http:
sbt/sbt#5226.

Here are two workarounds in sbt:

  • Set fork := true, so the IOApp runs in a separate process. That way when you hit Ctrl-C, sbt will kill the process, not just interrupt a thread. IOApp will take care of shutting down the Mu server cleanly in a shutdown hook.
  • Set Global / cancelable := false. This will disable the "try to cancel by interrupting" behaviour and revert sbt to its pre-1.3.x behaviour. Hitting Ctrl-C will exit sbt completely. (Again, IOApp will take care of shutting down the Mu server cleanly in a shutdown hook).

Out of these two options, I would say fork := true is preferred. Having sbt unexpectedly quit when you hit Ctrl-C can be quite irritating.

I'll add fork := true to build.sbt in the g8 template.