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

Support graceful shutdown in Netty server #3294

Merged
merged 16 commits into from
Nov 7, 2023

Conversation

kciesielski
Copy link
Member

@kciesielski kciesielski commented Nov 2, 2023

Resolves #3000

This PR enhances Netty server with graceful shutdown.

  1. Runs with stop() from the server binding
  2. Waits for in-flight requests to complete, then abruptly closes all client channels, server channel, and proceeds with shutting down event loop group.
  3. Wait time is 10s by default, can be adjusted or disabled in NettyConfig
  4. During the wait period, server rejects all new incoming requests with 503.

This feature should work with Future, ZIO, and Cats Effect Netty servers.

Tests rely heavily on time, so I'm concerned they may be flaky and need some reworks.

@kciesielski kciesielski added the enhancement New feature or request label Nov 6, 2023
@kciesielski kciesielski requested a review from adamw November 6, 2023 15:57
@kciesielski kciesielski marked this pull request as ready for review November 6, 2023 15:57
@kciesielski kciesielski changed the title [WIP] Support graceful shutdown in Netty server Support graceful shutdown in Netty server Nov 6, 2023
@@ -2044,7 +2044,8 @@ lazy val examples: ProjectMatrix = (projectMatrix in file("examples"))
scalaTest.value
),
libraryDependencies ++= loggerDependencies,
publishArtifact := false
publishArtifact := false,
Compile / run / fork := true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is that temporary for development or a new requirement?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is useful for development, when one wants to run examples with examples/runMain a.b.c.HelloExample and terminate a running server.

@@ -63,6 +63,21 @@ NettyFutureServer(NettyFutureServerOptions.customiseInterceptors.serverLog(None)
NettyFutureServer(NettyConfig.defaultNoStreaming.socketBacklog(256))
```

## Graceful shutdown

A Netty server has to be properly closed using function `NettyFutureServerBinding.stop()` (and analogous functions available in Cats and ZIO bindings). This function ensures that the server will wait at most 10 seconds for in-flight requests to complete, while rejecting all new requests with 503 during this period. Afterwards, it closes all server resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

has -> should? you can still just kill the server ...

/** Exposes additional `stop` effect, which allows stopping the server inside your test. It will be called after the test anyway (assuming
* idempotency), but may be useful for some cases where tests need to check specific behavior like returning 503s during shutdown.
*/
def serverWithStop(routes: NonEmptyList[ROUTE], gracefulShutdownTimeout: Option[FiniteDuration] = None): Resource[IO, (Port, IO[Unit])] =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe that's for a subsequent PR, but maybe it would make sense ot leave this as the only method that has to be implemented by specific test server interpreters? Then server can be implemented in terms of this function, by dropping the second element of the typle


class ServerGracefulShutdownTests[F[_], OPTIONS, ROUTE](createServerTest: CreateServerTest[F, Any, OPTIONS, ROUTE])(implicit
m: MonadError[F],
sleeper: Sleeper[F]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe this can be a normal parameter?

def testServer(name: String, rs: => NonEmptyList[ROUTE])(
runTest: (SttpBackend[IO, Fs2Streams[IO] with WebSockets], Uri) => IO[Assertion]
): Test

def testServerWithStop(name: String, rs: => NonEmptyList[ROUTE], gracefulShutdownTimeout: Option[FiniteDuration])(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similarly here ... maybe we can reduce the number of variants here by just supporting the stop-variants, and the others would delegate here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these used in fact? :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ok they are ... still seems we might have to many similar variants, making this hard to understand

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I didn't want to mess up other servers, so I thought to make the default implementation implement testServerWithStop in terms of testServer. I'll add some scaladoc to make it less confusing for now.

Copy link
Member

@adamw adamw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work! :) Some organizational comments inline

@@ -63,6 +63,21 @@ NettyFutureServer(NettyFutureServerOptions.customiseInterceptors.serverLog(None)
NettyFutureServer(NettyConfig.defaultNoStreaming.socketBacklog(256))
```

## Graceful shutdown

A Netty should can be gracefully closed using function `NettyFutureServerBinding.stop()` (and analogous functions available in Cats and ZIO bindings). This function ensures that the server will wait at most 10 seconds for in-flight requests to complete, while rejecting all new requests with 503 during this period. Afterwards, it closes all server resources.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should can? ;) maybe just run it through chatgpt to iron out the english :)

Copy link
Member

@adamw adamw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's merge - and maybe you could try to see if the test infrastructure could be somehow simplified? If it's more than 1-2h of effort, probably not worth it

@kciesielski kciesielski merged commit cee01ca into master Nov 7, 2023
13 checks passed
@mergify mergify bot deleted the 3000-netty-graceful-shutdown branch November 7, 2023 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request Netty
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Netty server does not shutdown gracefully
2 participants