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

Http2FrameListener-based HTTP/2 server handler implementation #10596

Merged
merged 23 commits into from
Mar 19, 2024
Merged

Conversation

yawkat
Copy link
Member

@yawkat yawkat commented Mar 11, 2024

Before this patch, HTTP/2 was implemented by taking a netty Http2MultiplexHandler, which creates a new netty channel for each HTTP/2 stream, and adding the HTTP/1.1 PipeliningServerHandler to each of those stream channels. This introduced unnecessary overhead due to the channel creation and the unnecessary features from PipeliningServerHandler.

This patch instead implements a Http2FrameListener directly, which is the lowest level netty HTTP/2 abstraction. This avoids the channel creation and simplifies some parts compared to PipeliningServerHandler (e.g. backpressure and no more need for pipelining). The implementation is split in two classes (MultiplexedServerHandler and Http2ServerHandler) in order to accommodate HTTP/3 in the future.

The new server handler has some missing features that I plan to add in future PRs, to keep this PR as minimal as possible:

  • (de)compression
  • h2c
  • server push
  • access logger
  • http/3
  • ping? (see comments)

When these changes are implemented, I plan to enable the new approach by default, likely in 4.4.0 still.

This new approach also removes some possibility for user pipeline customization. The only thing in the netty pipeline now, after the SSL handler, is the HTTP/2 decoder. It directly passes the HTTP/2 frames to the Micronaut Http2ServerHandler, without an intermediate step to HTTP/1.1 (Http2ServerHandler still converts the messages to HTTP/1.1 internally, so that RoutingInboundHandler can remain as-is, but the messages are not sent on any netty pipeline as before). This means e.g. logbook will not work. For this reason I still plan to keep the option of reverting to the old multiplexing approach, at least until 5.0.0. It is fairly low-maintenance anyway.

In a few places, I used the pattern where reactive calls would be dispatched to the event loop if they weren't already on the loop. However, if there is a reactive call outside the event loop, and then immediately a reactive call *on* the event loop, the calls can run in the wrong order. The second call on the event loop was not delayed with an execute() call, so it could run before the first call runs.

This fix introduces a central EventLoopSerializer that takes care of this problem. In the above problem scenario, it takes care to also delay the second call even though it was submitted on the event loop.
# Conflicts:
#	http-server-netty/src/main/java/io/micronaut/http/server/netty/handler/PipeliningServerHandler.java
# Conflicts:
#	http-server-netty/src/main/java/io/micronaut/http/server/netty/handler/PipeliningServerHandler.java
@yawkat yawkat added the type: improvement A minor improvement to an existing feature label Mar 11, 2024
@yawkat yawkat added this to the 4.4.0 milestone Mar 11, 2024
@yawkat yawkat requested review from timyates and graemerocher March 11, 2024 09:03
Copy link
Contributor

@timyates timyates left a comment

Choose a reason for hiding this comment

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

👍

Comment on lines +738 to +740
* As of 4.4.0, this approach was replaced with a more performant HTTP/2-specific
* implementation. This means worse compatibility with HTTP/1.1-based code paths and
* customizers, however. Setting this option to {@code true} returns to the old behavior.
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean that unless this is true, Http/1.1 services will notice a slowdown?

Are we going to keep this as true for the forseeable future? (I see the field is marked "TODO") 🤔

It's also something to rememeber if there's Http/2 benchmarks out there...

Copy link
Member Author

Choose a reason for hiding this comment

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

no, http/1.1 code paths are basically unchanged

i plan to make this false by default when compression and maybe server push is implemented

s.onSubscribe(new Subscription() {
@Override
public void request(long n) {
synchronized (DelayedSubscriber.this) {
Copy link
Contributor

Choose a reason for hiding this comment

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

can we use read/write locks for all these synchronised blocks?

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 but this sync block is fine for loom because it only guards memory access (no big function calls or IO)

} else if (demand != 0) {
upstream.request(demand);
} else {
assert cancel;
Copy link
Contributor

Choose a reason for hiding this comment

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

this requires enabling assertions in the JDK, should this be an illegal statue exception?

Copy link
Member Author

Choose a reason for hiding this comment

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

cancel is always true here because if none of the branches are hit, we would have returned above. intellij complained when i had if (cancel) here because it was able to figure out that cancel was always true, so i changed it to an assert.

@@ -116,6 +116,7 @@ enum ChannelRole {
/**
* The channel is a channel representing an individual HTTP2 stream.
*/
// todo: deprecate
Copy link
Contributor

Choose a reason for hiding this comment

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

address this TODO

Copy link
Member Author

Choose a reason for hiding this comment

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

can only deprecate when the new impl is actually enabled by default

Copy link
Contributor

Choose a reason for hiding this comment

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

You can put @NextMajorVersion

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 want to deprecate it in 4.5 already

@graemerocher graemerocher requested a review from dstepanov March 12, 2024 16:03
yawkat added 2 commits March 13, 2024 11:27
# Conflicts:
#	http-server-netty/src/main/java/io/micronaut/http/server/netty/NettyEmbeddedServices.java
Copy link

@@ -196,6 +196,7 @@ public class NettyHttpServerConfiguration extends HttpServerConfiguration {
private List<NettyListenerConfiguration> listeners = null;
private boolean eagerParsing = DEFAULT_EAGER_PARSING;
private int jsonBufferMaxComponents = DEFAULT_JSON_BUFFER_MAX_COMPONENTS;
private boolean legacyMultiplexHandlers = true; // TODO
Copy link
Contributor

Choose a reason for hiding this comment

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

remove this TODO

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 want to change this in 4.5

@yawkat yawkat merged commit a753099 into 4.4.x Mar 19, 2024
17 checks passed
@yawkat yawkat deleted the multiplex branch March 19, 2024 10:56
yawkat added a commit that referenced this pull request Apr 12, 2024
Another feature for #10596. Once again no separate tests, this functionality is covered by Http2ServerPushSpec when legacy-multiplex-handlers is off.
yawkat added a commit that referenced this pull request Apr 12, 2024
Another feature for #10596.

The session ID test fails because micronaut-session uses a channel attribute that we cannot support with HTTP/2. It's flawed even for HTTP/1, but would probably need an API change to fix.
sdelamo pushed a commit that referenced this pull request Apr 16, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Another feature for #10596. Once again no separate tests, this functionality is covered by Http2ServerPushSpec when legacy-multiplex-handlers is off.
sdelamo pushed a commit that referenced this pull request Apr 16, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
Another feature for #10596.

The session ID test fails because micronaut-session uses a channel attribute that we cannot support with HTTP/2. It's flawed even for HTTP/1, but would probably need an API change to fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: improvement A minor improvement to an existing feature
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

None yet

4 participants