I'm using http4s BlazeServer 0.21, how can I graceful shutdown? I want to reject all upcoming requests, and keep process unfinished requests and response back, within a hard shutdown time.
I tried starting server with serveWhile
and set a shutdownHook SignallingRef
. The server stream & middleware defer as expected (so our metrics & log middleware still log this response)
//serverStream
for {
signal <- fs2.Stream.eval(SignallingRef[F, Boolean](false))
exitCode <- fs2.Stream.eval(Ref[F].of(ExitCode.Success))
_ <- fs2.Stream.eval(shutdown(signal))
server <- BlazeServerBuilder[F]
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApp)
.serveWhile(signal, exitCode)
} yield server
def shutdown[F[_]: Effect](interrupter: SignallingRef[F, Boolean]): F[Unit] = {
LiftIO[F].liftIO(IO {
sys.addShutdownHook {
...
interrupter.set(true)
}
})
}
object Server extends IOApp {
def run(args: List[String]): IO[ExitCode] =
serverStream[IO].compile.drain.as(ExitCode.Success)
}
but the http server doesn't work as I expect, seems like http4s's internal ServerChannel
has its own shutdownHook and cancel all the responses already.
any suggestion/workaround? or maybe just a way to hold and don't kill requests for x
seconds is also appreciated.
The server is hooked for SIGTERM as a matter of convenience.
As a convenience, cats-effect provides an cats.effect.IOApp trait with an abstract run method that returns a IO[ExitCode]. An IOApp runs the process and adds a JVM shutdown hook to interrupt the infinite process and gracefully shut down your server when a SIGTERM is received.
And if you want to shutdown using an URL e.g. http://localhost:8080/ops/shutdown/true
, it works too.
The code is pretty straight forward,
class SysOpsEndpoints[F[_]: Sync](signal: SignallingRef[F, Boolean]) extends Http4sDsl[F] {
private def shutdown: HttpRoutes[F] =
HttpRoutes.of[F] {
case GET -> Root / "shutdown" / shutdown =>
for {
_ <- signal.set(Try(shutdown.toBoolean).getOrElse(false))
result <- Ok(s"Shutdown: $shutdown")
} yield result
}
}
object SysOpsEndpoints {
def endpoints[F[_]: Sync](signal: SignallingRef[F, Boolean]): HttpRoutes[F] =
new SysOpsEndpoints(signal).shutdown
}
and the setup for the server for-comprehension is similar to yours,
for {
signal <- fs2.Stream.eval(SignallingRef[F, Boolean](false))
exitCode <- fs2.Stream.eval(Ref[F].of(ExitCode.Success))
httpApp = Router(
"/ops" -> SysOpsEndpoints.endpoints(signal)
).orNotFound
server <- BlazeServerBuilder[F](serverEc)
.bindHttp(8080, "0.0.0.0")
.withHttpApp(httpApp)
.serveWhile(signal, exitCode)
} yield server
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With