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

[master]: Stop Netty server reloading configuration on each request #8335

Merged
merged 7 commits into from Apr 18, 2018

Conversation

richdougherty
Copy link
Member

Fixes #7812.

The caching of the ReloadCache uses reference equality so we need to be more careful when passing around the Try[Application] returned by ApplicationProvider.get. We need to make sure we keep the same reference.

Also did some refactoring of code while I was making these changes.

@richdougherty
Copy link
Member Author

2.6.x version of this PR: #8334.

@richdougherty
Copy link
Member Author

I couldn't work out a way to unit test configuration loading without adding intrusive instrumentation into the server code so I plan to verify this by hand before merging. 🤷‍♂️

* NOTE: This will use the ApplicationProvider of the server to get the application instance.
* Use {@code Server.getHandlerFor(request, tryApp)} to pass a specific application instance
*/
@deprecated("Use Server.getHandlerFor(request, tryApp) instead", "2.7.0")
Copy link
Member Author

Choose a reason for hiding this comment

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

As of #8334 this method will be deprecated in 2.6.13. The method is highly unlikely to be used outside Play so I think it's OK to remove it for 2.7.0.

Copy link
Member

Choose a reason for hiding this comment

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

👍

Anyway, maybe is worth to add a note in Migration27.md.

request: RequestHeader,
tryApp: Try[Application]
): Either[Future[Result], (RequestHeader, Handler, Application)] = {
): (RequestHeader, Handler) = {
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's easier to return a Handler than an Either[Future[Result], Handler], since the returned Future[Result] is always just turned into a Handler anyway...

Also the returned Application is not helpful if we're trying to preserve the reference equality of the Try[Application].

@@ -84,40 +82,31 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader

val tryRequest: Try[RequestHeader] = modelConversion(tryApp).convertRequest(channel, request)

def clientError(statusCode: Int, message: String) = {
def clientError(statusCode: Int, message: String): (RequestHeader, Handler) = {
Copy link
Member Author

Choose a reason for hiding this comment

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

Quite a lot of code in this PR now uses a (RequestHeader, Handler) pair instead of other types. This is probably something we could represent as a special type at some point. Maybe there's a state monad in here for the RequestHeader...

@@ -264,12 +247,10 @@ private[play] class PlayRequestHandler(val server: NettyServer, val serverHeader
* Handle an essential action.
*/
private def handleAction(action: EssentialAction, requestHeader: RequestHeader,
request: HttpRequest, app: Option[Application]): Future[HttpResponse] = {
implicit val mat: Materializer = app.fold(server.materializer)(_.materializer)
request: HttpRequest, tryApp: Try[Application]): Future[HttpResponse] = {
Copy link
Member Author

Choose a reason for hiding this comment

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

This line is the key fix. We're ensuring we pass the original Try[Application] through.

val unparsedTarget = modelConversion(tryApp).createUnparsedRequestTarget(request)
val requestHeader = modelConversion(tryApp).createRequestHeader(channel, request, unparsedTarget)
val result = errorHandler(server.applicationProvider.current).onClientError(requestHeader, statusCode,
Copy link
Member Author

Choose a reason for hiding this comment

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

This line is the only behaviour change I made in the refactoring—using tryApp instead of applicationProvider.current—however I think the new code is the correct behaviour.

@pettyjamesm
Copy link
Contributor

pettyjamesm commented Apr 4, 2018

Flame graphs of running the akka http server show that it has the same or similar problem: configuration being loaded every request. Here's the flame graph referenced, sampled from a server that's been online for 12+ hours, well beyond the initial loading:

screen shot 2018-04-04 at 10 38 16 am

@marcospereira marcospereira changed the title Stop Netty server reloading configuration on each request [master]: Stop Netty server reloading configuration on each request Apr 4, 2018
Copy link
Member

@marcospereira marcospereira left a comment

Choose a reason for hiding this comment

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

LGTM, too.

@@ -225,25 +225,6 @@ class AkkaHttpServer(
}
}

private def getHandler(
Copy link
Member

Choose a reason for hiding this comment

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

👍

private def errorHandler(app: Option[Application]): HttpErrorHandler =
app.fold[HttpErrorHandler](DefaultHttpErrorHandler)(_.errorHandler)
private def errorHandler(tryApp: Try[Application]): HttpErrorHandler =
tryApp.fold[HttpErrorHandler](_ => DefaultHttpErrorHandler, _.errorHandler)
Copy link
Member

Choose a reason for hiding this comment

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

The master branch build will have the same problem with Scala 2.11 (no fold for Try).

Since we will drop Scala 2.11, we can add a comment here to use fold when dropping Scala 2.11.

* NOTE: This will use the ApplicationProvider of the server to get the application instance.
* Use {@code Server.getHandlerFor(request, tryApp)} to pass a specific application instance
*/
@deprecated("Use Server.getHandlerFor(request, tryApp) instead", "2.7.0")
Copy link
Member

Choose a reason for hiding this comment

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

👍

Anyway, maybe is worth to add a note in Migration27.md.

@richdougherty
Copy link
Member Author

richdougherty commented Apr 5, 2018

I couldn't work out a way to unit test configuration loading without adding intrusive instrumentation into the server code so I plan to verify this by hand before merging.

Due to @pettyjamesm's report of issues with Akka HTTP in addition to known issues (#7812) with Netty I've decided to be more conservative and there is now instrumentation in the server and automated tests to check reloads.

The instrumentation is enabled with a configuration setting so it can be used during testing but is disabled in production. Instrumentation details are passed as a request attribute. This is one of those times it would be nice to have JMX hooks so we can view internal server stats. Using a request attribute is the next best thing though.

Reload counts now appear to be correct on both backends.

@richdougherty
Copy link
Member Author

Got this probably transient failure on one test, restarting.

[error]   x Not reap a non-expired file
[error]    List(/tmp/8627293184609446381/notcollected.txt) is not empty (TemporaryFileReaperSpec.scala:79)

@richdougherty
Copy link
Member Author

I've run the the HelloWorld microbenchmark before and after this change and the change does seem to improve performance.

Copy link
Member

@gmethvin gmethvin left a comment

Choose a reason for hiding this comment

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

lgtm except a few suggestions on the migration guide

@@ -92,3 +92,7 @@ Like announced in the [[Play 2.6 Migration Guide|Migration26#Java-Form-Changes]]
### `Guava` version updated to 24.0-jre

Play 2.6.x provided 23.0 version of Guava library. Now it is updated to last actual version, 24.1-jre. Lots of changes were made in library, you can see the full changelog [here](https://github.com/google/guava/releases).

### `Server.getHandlerFor` has been removed
Copy link
Member

Choose a reason for hiding this comment

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

I would write Server#getHandlerFor which is the common notation for instance methods.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK!


### `Server.getHandlerFor` has been removed

This method on the `Server` trait was used internally by the Play server code when routing requests. It has been removed and replaced with a method of the same name on the `Server` object.
Copy link
Member

Choose a reason for hiding this comment

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

You should either mention that the new method is private[server] or not mention it at all since it's not public API.

Copy link
Contributor

Choose a reason for hiding this comment

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

I would mention it. Last time I tried to figure out new relic's tracing logic, this was a "method of interest" and changing the semantics around it might have broken some visibility for a significant number of users, especially for a patch release.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, fine with me, as long as we note that it's not public API.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll make an "internal API changes" section in the migration documentation and we'll try to mention any notable changes here. No guarantees though because we do make quite a few internal changes and it'll be hard to track everything.

Fixes playframework#7812.

The caching of the ReloadCache uses reference equality so we need
to be more careful when passing around the Try[Application] returned
by ApplicationProvider.get. We need to make sure we keep the same
reference.

Also did some refactoring of code while I was making these changes.
@richdougherty
Copy link
Member Author

Migration guide updated, merging.

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 this pull request may close these issues.

None yet

4 participants