-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
HttpInput refactoring #4556
HttpInput refactoring #4556
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just some thoughts from a light review in passing...
jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInputOverHTTP.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AbstractHttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AbstractLockedHttpInput.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
review in progress...
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
Outdated
Show resolved
Hide resolved
HttpInput.Content content = takeContent(endStreamResult); | ||
if (content != null) | ||
return content; | ||
getStream().demand(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not particularly fond of this - I'm sure it can be done otherwise.
The demand mechanism is well established now (even present in the JDK) so I would not change its semantic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason why I introduced this mechanism is to make HTTP 1 and 2 more similar and avoid triggering data demands until we reach a safe spot in the state machine, much like HTTP 1 makes the distinction between parseAndFill
and setting fillInterested
.
This drastically helps controlling the paths that could lead to race conditions as the latter can only happen wherever we decide they can happen instead of virtually everywhere, making debugging a lot more manageable.
I tried really hard without touching this mechanism and the fact that you can't read data without triggering an async call that adds content makes the code, IMHO, virtually unmaintainable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to discuss this more, as we have certainly figured out more complex cases, and I don't feel this one is impossible to solve without breaking the demand semantic - which would potentially introduce a much larger problem.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I agree.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about just adding an available()
method to the interface. All that is necessary is something that can be used to query to decide if we should continue iterating on content or exit and let HttpChannel.unhandle
do the demand(1)
to setup to receive more content. So long as we don't have content arriving between those two events, then things become simpler.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea of adding an available()
method a lot: it's a cleaner interface, trivial to implement and provides the necessary semantic.
Just a note on status of my much interrupted redux review of this PR. I am working through the interaction diagrams of the main flows for h1|h2 x blocking| async. I have still some more work to do there to get a good overall picture before deciding how I think it should change.... but I'll note down my current thinking here & now, just incase I get interrupted again and forget. The issue for me is how in some scenarios the callbacks are doing the content processing, while in others they don't. Thecrux for my is h2 callbacks with call addContent, which in turn calls available (to see if that raw content actually produced real content) and then decides if handle should be called or not. This is done outside of the normal handle cycle, so it is racey and needs protection etc. So I think we can greatly improve this by not having addContent call available. The h2 callbacks will still add the raw content, but it will not be immediately processed, instead they will return true if a wakeup is needed and then we will call HttpChannel.handle, which should be in the REGISTER (or may PRODUCING???) state and it can call available() (or something similar) to process raw content to see if there is real content and then decide to call onReadPossible or not. The processing is then done in the better protected handling loop, the same as for h1..... at least that is my bubble of an idea.. Lot's more detailed analysis still needed for the exact state transitions. |
@lorban don't forget the interaction diagrams! I know you are going step by step towards your destination.... but we don't want you to get there only to discover that the usual malcontents didn't want to go there in the first place! Please have a go of updating them to what you think they should be and check them in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couple of little things while I get on with deeper review...
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Show resolved
Hide resolved
jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
Show resolved
Hide resolved
jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2StreamEndPoint.java
Outdated
Show resolved
Hide resolved
...y-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/ContentDemander_state.puml
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java
Show resolved
Hide resolved
if (content != null) | ||
return content; | ||
|
||
_semaphore.drainPermits(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really REALLY don't like having drainPermits. This should not be necessary
boolean handleRequest = onRequestComplete(); | ||
handle |= handleContent | handleRequest; | ||
} | ||
|
||
boolean woken = getRequest().getHttpInput().wakeup(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is wrong because it means a spurious wakeup can occur even if needContent
has not been called.
A call to produceContent
can do demand, so onData
may be called at any time afterwards... so it is wrong for it to wakeup HttpInput if needContent has not been called.
This class needs to know if needContent
has been called and only do a wakeup
if it has.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about this, the more I'm convinced we need a dedicated onContentAvailable
callback that is only called if needContent
has been called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could almost pass a Callback
in needContent to make the contract clear!
jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
Outdated
Show resolved
Hide resolved
jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/ContentProducer.java
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java
Outdated
Show resolved
Hide resolved
boolean isError(); | ||
|
||
/** | ||
* This call may block. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this call may block? Isn't it wrong that there is a blocking method if it can be implemented by AsyncContentProducer
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because of poor javadoc. What I meant is that some implementations may decide to make nextContent()
a blocking call, some others may not. This is exactly what BlockingContentProducer
and AsyncContentProducer
do but I'd like the javadoc to avoid speaking about specific implementations.
// events like timeout: we get notified and either schedule onError or release the | ||
// blocking semaphore. | ||
private HttpInput.Content _content; | ||
private HttpInput.Content _specialContent; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not true that _specialContent
is only accessed by one thread. It is set by the scheduler thread during idle timeouts.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My original analysis was that the scheduler thread only ever writes to _specialContent
, then either notifies the blocked thread with the semaphore (which gives us a happens-before) or reschedules a thread that reads it. A job submission to a standard ExecutorService
creates a happens-before relationship that would make this safe but I may need to check if the form of scheduling we use in EWYK does too. I suspect it does, but I'll review this in more details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks safe to me, but I could mark _specialContent
as volatile nevertheless. This would make the code a bit less brittle in wrt potential future changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need both _content
and _specialContent
. We only have a single content in H2 and I think similar logic could be applied here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got rid of _specialContent.
jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java
Outdated
Show resolved
Hide resolved
|
||
_semaphore.drainPermits(); | ||
_httpChannel.getState().onReadUnready(); | ||
if (_httpChannel.needContent()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It needs to be PRECISELY that only when needContent returns false will there be some callback that will release the semaphore. So we need to only ever acquire the semaphore in that case. We then don't need drain permits. I'm still thinking needContent(Callback) is the way to go.
@Override | ||
public boolean wakeup() | ||
{ | ||
// Calling _asyncContentProducer.wakeup() breaks AsyncIOServletTest.testAsyncWriteThrowsError H2C. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, feels weird that we are not forwarding to _asyncContextProducer
-- if really not needed, it deserves a comment.
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelState.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpConnection.java
Outdated
Show resolved
Hide resolved
jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java
Outdated
Show resolved
Hide resolved
jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java
Outdated
Show resolved
Hide resolved
jetty-server/src/test/java/org/eclipse/jetty/server/BlockingContentProducerTest.java
Show resolved
Hide resolved
...st-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java
Show resolved
Hide resolved
...est-http-client-transport/src/test/java/org/eclipse/jetty/http/client/TransportScenario.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/BlockingContentProducer.java
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, apart few niceties and a little more javadocs.
jetty-fcgi/fcgi-server/src/main/java/org/eclipse/jetty/fcgi/server/HttpChannelOverFCGI.java
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Show resolved
Hide resolved
else if (c == null) | ||
{ | ||
if (!content.isSpecial()) | ||
throw new AssertionError("Non special content without demand"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I insist that we cannot throw, even if it's a bug.
Throwing will cause the server to hang and require a restart, while failing the callback will fail the current request and allow for future requests.
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Show resolved
Hide resolved
jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/AsyncContentProducer.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannel.java
Outdated
Show resolved
Hide resolved
jetty-server/src/main/java/org/eclipse/jetty/server/HttpChannelOverHttp.java
Outdated
Show resolved
Hide resolved
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
…CGI (Milestone 3) Signed-off-by: Ludovic Orban <lorban@bitronix.be>
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
…to HttpChannel* impls (Milestone 5) Signed-off-by: Ludovic Orban <lorban@bitronix.be>
…tead. Make EOF and errors be special content. Transition to a much simplified FSM by using the needContent() / produceContent() model. Implement blocking on top of async, this way there is only one FSM. (Milestone 6) Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This is a work in progress, the PR is only about exercising the code against the full test suite under Jenkins. As a reminder, this work was triggered by #4318.
Old status:
HttpInput*
implementations pass the full test suite (Milestone 3)Current status:
addContent()
by makingproduceContent()
returnContent
instead.Next steps: