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

EagerContentHandler. #9051 #12077

Open
wants to merge 72 commits into
base: jetty-12.1.x
Choose a base branch
from

Conversation

gregw
Copy link
Contributor

@gregw gregw commented Jul 23, 2024

Fix #9051 with EagerContentHandler to replace DelayedHandler

The make the EagerContentHandler.RetainedContentLoader work efficiently this PR adjusted the buffering strategy of h1, h2 and h3 to keep and reuse a retained buffer until it is mostly full.

Also fixed several bugs in XmlConfiguration:

  • A Set could not be used with a builder style method (kind of missing feature more than a bug)
  • If a property element was used in a Set it could only be a string and the type element was ignored.
  • When trying to find a native match, the vClass local variable was overwritten with the native TYPE being tried. This affected subsequent match attempts using the wrong vClass

@gregw
Copy link
Contributor Author

gregw commented Jul 23, 2024

@sbordet @lorban Your thoughts on doing this? If you think it is a good idea, can you contribute the h2 and h3 delayed dispatch handling to this branch.
I will then change the DelayedHandler to only be for delaying to read the entire content (buffering to disk or parsing multi-part or form content).

@lorban
Copy link
Contributor

lorban commented Jul 23, 2024

Implementation-wise, this looks alright assuming the same logic can be replicated in H2/H3 (but I doubt this is going to be troublesome).

But could you summarize what benefits/drawbacks this has over the existing DelayedHandler both for clarity and posterity?

@gregw
Copy link
Contributor Author

gregw commented Jul 23, 2024

@lorban wrote:

could you summarize what benefits/drawbacks this has over the existing DelayedHandler both for clarity and posterity?

The DelayedHandler is not able to truly delay the dispatch to the next handler without an execute. This is because, unlike the handle call, the demand callback is serialized with other demand/write callbacks. So if a demand callback is used to call the next handler handle method, it can dead lock if blocks waiting on a demand/write callback. Thus we need to dispatch, which kind of defeats the whole purpose of avoiding scheduling delays on the initial dispatch without content.

@gregw
Copy link
Contributor Author

gregw commented Jul 26, 2024

@sbordet @lorban Can you take this one on

@gregw
Copy link
Contributor Author

gregw commented Aug 6, 2024

I've also taken the opportunity to cleanup lots of deprecation and TODOs in HttpConnection

@gregw
Copy link
Contributor Author

gregw commented Aug 7, 2024

@lachlan-roberts Can you take over the handling of delayed multipart in this PR?

@gregw
Copy link
Contributor Author

gregw commented Aug 23, 2024

@sbordet can you do the h2 handling for this
@lorban can you do the h3 handling for this

…tty-12.1.x/delayedDispatch

# Conflicts:
#	jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/internal/HttpConnection.java
#	jetty-core/jetty-tests/jetty-test-client-transports/src/test/java/org/eclipse/jetty/test/client/transport/AbstractTest.java
#	jetty-ee10/jetty-ee10-servlet/src/test/java/org/eclipse/jetty/ee10/servlet/ServletTest.java
#	jetty-ee11/jetty-ee11-servlet/src/test/java/org/eclipse/jetty/ee11/servlet/ServletTest.java
#	jetty-ee9/jetty-ee9-servlet/src/test/java/org/eclipse/jetty/ee9/servlet/ServletTest.java
@gregw
Copy link
Contributor Author

gregw commented Sep 15, 2024

@sbordet can you do the h2 handling for this
@lorban can you do the h3 handling for this

 + rename save to hold
 + added "Bytes" to configuration
@gregw gregw changed the title Cleanup DelayedDispatch handling. #9051 Improve DelayedHandler. #9051 Nov 8, 2024
@gregw gregw requested a review from sbordet November 8, 2024 21:19
@gregw
Copy link
Contributor Author

gregw commented Nov 8, 2024

The HTTP/3 implementation is to be done.

@lorban I've implemented for H3, but I don't know if it needs the same holdBuffer logic as h2. Do you think there is a release race? We will see from tests I guess....

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

Not another configuration parameter, see comment.

@gregw gregw requested a review from sbordet November 11, 2024 21:01
@gregw gregw changed the title Improve DelayedHandler. #9051 EagerContentHandler. #9051 Nov 12, 2024
@gregw
Copy link
Contributor Author

gregw commented Nov 12, 2024

@sbordet I ended up making it extensible, with a EagerContentHandler containing map of mime type to ContentLoaderFactory, each which can create a ContentLoader instance.
This allows configuration that is specific to a particular ContentLoader to be configured on its factory rather than on the Handler. The Handler now has not configuration other than the factories.

I've not yet finished the modules and xml, but I've started, so you can see how this design makes that configuration make more sense (note that I'll be putting the optional non default MultiPartConfig in its own module).

…tty-12.1.x/delayedDispatch

# Conflicts:
#	jetty-core/jetty-server/src/main/java/org/eclipse/jetty/server/FormFields.java
Copy link
Contributor

@lorban lorban left a comment

Choose a reason for hiding this comment

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

I quite like this new EagerContentHandler, the way its implemented and made configurable.

Besides that new helper in the MultiPartConfig builder which I find its implemented wrong, this LGTM.

@gregw
Copy link
Contributor Author

gregw commented Nov 12, 2024

I found several bugs in XmlConfiguration that prevented it being used with a Builder pattern. This PR now fixes them so we can build a MultiPartConfig in XML. Adding @lachlan-roberts to reviewers so he can check that.

jetty-core/jetty-server/src/main/java/module-info.java Outdated Show resolved Hide resolved
* @param maxFields The maximum number of fields to accept
* @param maxLength The maximum length of fields
* @param maxFields The maximum number of fields to accept; or -1 for a default
* @param maxLength The maximum length of fields; or -1 for a default
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a bit misleading in a way, I think it was clearer before.

Because you can configure these to be -1 for no limit on the length.

So now if the context attribute of MAX_LENGTH_ATTRIBUTE was set to -1, then it is unlimited, but if you call this method with -1 it is limited to MAX_LENGTH_DEFAULT.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree it is a bit confusing, but I need a way to pass in "use default value", otherwise we end up encoding default values in XML (see jetty-eager-content.xml) and in java. Do you have an alternative suggestion?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's also an issue if you want to use the default for maxFields, but pass in a maxLength value. With the current API, once you choose to pass in values, you have to pass in both and cannot access the default.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just tried added a <Default> element to the XML to get the constants from Java, but the <Arg> element doesn't allow a <Default> element :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@lachlan-roberts and finally... the other API is deprecated, so it will soon go away. You can pass in MAX_VALUE for unlimited.

Copy link
Contributor

@sbordet sbordet left a comment

Choose a reason for hiding this comment

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

I like this approach much better.

Mostly nits, but few more important ones.

Documentation and DistributionTests are missing, but I can add them.

<Item>
<New class="org.eclipse.jetty.server.handler.EagerContentHandler$RetainedContentLoaderFactory">
<Arg type="int"><Property name="jetty.eager.retained.maxRetainedBytes" default="-1"/></Arg>
<Arg type="int"><Property name="jetty.eager.retained.frameOverhead" default="-1"/></Arg>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<Arg type="int"><Property name="jetty.eager.retained.frameOverhead" default="-1"/></Arg>
<Arg type="int"><Property name="jetty.eager.retained.framingOverhead" default="-1"/></Arg>

<New class="org.eclipse.jetty.server.handler.EagerContentHandler$RetainedContentLoaderFactory">
<Arg type="int"><Property name="jetty.eager.retained.maxRetainedBytes" default="-1"/></Arg>
<Arg type="int"><Property name="jetty.eager.retained.frameOverhead" default="-1"/></Arg>
<Arg type="boolean"><Property name="jetty.eager.retained.reject" default="false"/></Arg>
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
<Arg type="boolean"><Property name="jetty.eager.retained.reject" default="false"/></Arg>
<Arg type="boolean"><Property name="jetty.eager.retained.rejectWhenExceeded" default="false"/></Arg>

</Arg>
</Call>

<!-- TODO fields -->
Copy link
Contributor

Choose a reason for hiding this comment

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

What fields?

@@ -0,0 +1,43 @@

Copy link
Contributor

Choose a reason for hiding this comment

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

Remove empty line.

# jetty.eager.retained.maxRetainedBytes=-1

## The frame overhead to use when calculating the retained bytes or -1 for a default
# jetty.eager.retained.frameOverhead=-1
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
# jetty.eager.retained.frameOverhead=-1
# jetty.eager.retained.framingOverhead=-1

Response.writeError(getRequest(), getResponse(), getCallback(), t);
}
// return false to indicate the passed request/response/callback were not used.
return false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Cannot return false here, because in all cases the response is generated and the callback completed.

This return value is only used in RetainedContentLoader.doHandle() to release the chunks, but since the callback is invoked in all cases, they will be released already.

Perhaps we do not need a boolean return type, just void.

{
release();
_callback.succeeded();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Override failed(Throwable) to release the chunks in case an error response fails.

{
private final Deque<Content.Chunk> _chunks = new ArrayDeque<>();
private final long _maxRetainedBytes;
private final int _chunkOverhead;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
private final int _chunkOverhead;
private final int _framingOverhead;

* @param frameOverhead The bytes to account for per chunk when calculating the size; or -1 for a heuristic.
* @param reject If {@code true} then requests are rejected if the content is not complete before maxRetainedBytes.
*/
public RetainedContentLoader(Handler handler, Request request, Response response, Callback callback, long maxRetainedBytes, int frameOverhead, boolean reject)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
public RetainedContentLoader(Handler handler, Request request, Response response, Callback callback, long maxRetainedBytes, int frameOverhead, boolean reject)
public RetainedContentLoader(Handler handler, Request request, Response response, Callback callback, long maxRetainedBytes, int framingOverhead, boolean rejectWhenExceeded)

? Math.max(1, request.getConnectionMetaData().getConnector().getConnectionFactory(HttpConnectionFactory.class).getInputBufferSize() - 1500)
: maxRetainedBytes;
_chunkOverhead = frameOverhead < 0
? (request.getConnectionMetaData().getHttpVersion() == HttpVersion.HTTP_2 ? 9 : 8)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also for HTTP/3, the framing depends on the content size, but it can only be at most 9 bytes.

So perhaps:

Suggested change
? (request.getConnectionMetaData().getHttpVersion() == HttpVersion.HTTP_2 ? 9 : 8)
? (request.getConnectionMetaData().getHttpVersion().getVersion() <= HttpVersion.HTTP_1_1.getVersion() ? 8 : 9)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: 👀 In review
Development

Successfully merging this pull request may close these issues.

5 participants