gRPC server does not shutdown after `sbt run`
cb372 opened this issue · 4 comments
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)
Also when we use IOApp
?
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
}
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 theIOApp
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.