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

Revert delayed dispatch handling to the connections. #9051 #12077

Draft
wants to merge 19 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. Revert to delayed content handling in the Connections

@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

@@ -52,12 +52,9 @@ protected AbstractConnection(EndPoint endPoint, Executor executor)
_readCallback = new ReadCallback();
}

@Deprecated
Copy link
Contributor

Choose a reason for hiding this comment

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

Please restore this, as it is fixed in #12266.

Comment on lines 59 to 60
// TODO consider removing the #fillInterested method from the connection and only use #fillInterestedCallback
// so a connection need not be Invocable
Copy link
Contributor

Choose a reason for hiding this comment

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

Please restore.

Comment on lines +26 to +27
import org.eclipse.jetty.http.MultiPartConfig;
import org.eclipse.jetty.http.MultiPartFormData;
Copy link
Contributor

Choose a reason for hiding this comment

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

If we restore delayed dispatch, I am not sure this Handler is necessary anymore.

My point being that we have one-liner helpers that do exactly the same without the need to have a Handler in the Handler chain.

For example, an application can call FormFields.from() as a one liner to do the same job.
We offer no synchronous API to get the Fields, so an application would always have to deal with a CompletableFuture<Fields>, so the DelayedHandler seems not that useful anymore.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sbordet We do offer synchronous APIs. The Servlet API is mostly blocking and is implemented on top of FormFields and MultiPart. The idea of the handler is that it can be put in front of an application that is written with blocking APIs (e.g. any multipart servlet). The handler would then asynchronously receive the content before propagating the handle call, allowing the blocking code to execute without blocking.

return true;
}

@Override
public boolean contentComplete()
{
// Do nothing at this point.
// Do nothing at this point unless we delayed for content
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
// Do nothing at this point unless we delayed for content
// Do nothing at this point unless we delayed for content.

Not on this line, but the _delayedForContent logic has not been ported to earlyEOF() nor for idle timeouts like it was in Jetty 11.

@@ -138,6 +139,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
}, "/post");

_connector.setIdleTimeout(idleTimeout);
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

@@ -141,6 +142,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
}, "/post");

_connector.setIdleTimeout(idleTimeout);
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

@@ -133,6 +134,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I
}), "/post");

_connector.setIdleTimeout(idleTimeout);
_connector.getConnectionFactory(HttpConnectionFactory.class).getHttpConfiguration().setDelayDispatchUntilContent(false);
Copy link
Contributor

Choose a reason for hiding this comment

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

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sbordet Good question... I can't remember exactly. With this line the test passes, but does log a "channel already completed" exception. Without this line the test fails with NPE exception and 500 response. Neither is good.... investigating...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@sbordet @lorban So I've worked out why I did this.... These tests behave differently with deferred dispatch because we have refined the idle/failure handling without considering delayed dispatch and we act differently if there is pending demand from the application or not.

Without delayed dispatch, we call the app, which tries to read, so we have an _onContentAvailable runnable to call and feed it an error chunk.
With delayed content, we have not called the app, so there is no content runnable, so we don't set an error chunk with the idle timeout, Thus the error that is seen is a failure to consume all content, which is a bit strange.

I'm thinking that what we should do is half the idletimeout when we delay dispatch, then when the idle timeout goes off, we do the dispatch anyway and let the other half of the idle timeout playout. That way we get identical behaviour with and without delayed dispatch.

Thoughts?

PS. I think we probably should walk through the idle timeout stuff again at some stage, as consumeAvailable gets called 2 or 3 times at the moment!

@lorban
Copy link
Contributor

lorban commented Sep 17, 2024

@gregw I'm going to take care of both H2 and H3 as they're going to be very similar.

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.

4 participants