-
Notifications
You must be signed in to change notification settings - Fork 2k
Issue #3806 - Make Async sendError fully Async #3912
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
Issue #3806 - Make Async sendError fully Async #3912
Conversation
Avoid using isHandled as a test withing sendError as this can be called asynchronously and is in a race with the normal dispatch of the request, which could also be setting handled status. Signed-off-by: Greg Wilkins <[email protected]>
The ErrorHandler was dispatching directly to a context from within sendError. This meant that an async thread can call sendError and be dispatched to within the servlet container at the same time that the original thread was still dispatched to the container. This commit fixes that problem by using an async dispatch for error pages within the ErrorHandler. However, this introduces a new problem that a well behaved async app will call complete after calling sendError. Thus we have ignore complete ISEs for the remainder of the current async cycle. Signed-off-by: Greg Wilkins <[email protected]>
Fixed the closing of the output after calling sendError. Do not close if the request was async (and thus might be dispatched to an async error) or if it is now async because the error page itself is async. Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
…06-async-sendError
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Reworked HttpChannelState and sendError so that sendError is now just a change of state. All the work is done in the ErrorDispatch action, including calling the ErrorHandler. Async not yet working. Signed-off-by: Greg Wilkins <[email protected]>
Additional tests Signed-off-by: Greg Wilkins <[email protected]>
Converted ERRORED state to a separate boolean so it can be used for both Sync and Async dispatches. Removed ASYNC_IO state as it was just the same as DISPATCHED The async onError listener handling is now most likely broken. Signed-off-by: Greg Wilkins <[email protected]>
WIP making sendError simpler and more tests pass Signed-off-by: Greg Wilkins <[email protected]>
WIP handling async and thrown exceptions Signed-off-by: Greg Wilkins <[email protected]>
WIP passing tests Signed-off-by: Greg Wilkins <[email protected]>
…06-async-sendError Signed-off-by: Greg Wilkins <[email protected]>
Improved thread handling Signed-off-by: Greg Wilkins <[email protected]>
removed bad test Signed-off-by: Greg Wilkins <[email protected]>
Implemented error dispatch on complete properly more fixed tests Signed-off-by: Greg Wilkins <[email protected]>
sendError state looks committed Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
- Added resetContent method to leave more non-content headers during sendError - Fixed security tests Signed-off-by: Greg Wilkins <[email protected]>
- simplified the non dispatch error page writing. Moved towards being able to write async Signed-off-by: Greg Wilkins <[email protected]>
But is not that pattern already racy for the async case? By the time you arrive at the finally block, an application spawned thread could be calling If the handling throws an exception, we will first go to the If the handling calls I think the sync case is ok, it's the async case that is racy. |
|
@gregw so I have doubts about the broken pattern that you found. |
|
@sbordet happy to do another review of this.... but the pattern was not racy.... Only a dispatched thread can call However, the new blocking sendError semantic can be a problem. If an exception is thrown, then we depart via the finally block and do the Note that we do have HttpChannel.Listener (why is that not an EventListener) which does give us a good completion callback that works for async and blocking, but that listener is a bit heavy weight and I'd really rather prefer that we didn't use it for something like sessions. I think @janbartel PR is now going to avoid this pattern entirely by having a dedicated completion callback to the request to deal with any session touched by that request. So I think we will be good for sessions (and this PR doesn't break anything that wasn't already a little broken). So I just think it is something we need to be aware of. Generally speaking... I'm wondering if there is a clever MethodHandle mechanism we could use to plug in listeners to specific callback points without the cost of a list and iteration for every possible callback point, even if we only want 1 method called back..... |
|
I think we need to merge this soon to avoid it going stale. |
Signed-off-by: Greg Wilkins <[email protected]>
…06-async-sendError-fullyAsync Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
| public Callback getClosedCallback() | ||
| { | ||
| return _closeCallback; | ||
| } |
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.
This getter/setter pair should be renamed to getCloseCallback() (without d), as they are the callback for method close(), and the field also is without d.
Furthermore, I don't like them. I would prefer to see a new close(Callback) method, so that close() will call close(BLOCKING_CLOSE_CALLBACK), etc.
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 have renamed, but can't do close(Callback) at this level as we need to be able to have close passed through a Writer chain as well :(
| _response.closeOutput(); | ||
| // ensure the callback actually got called | ||
| if (_response.getHttpOutput().getClosedCallback() != null) | ||
| _response.getHttpOutput().getClosedCallback().succeeded(); |
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 don't understand this code.
It sets the close callback to a non-null one, then calls _response.closeOutput() which calls HttpOutput.close(), which nulls out _closeCallback immediately. So it's always the case that the close callback is null.
On the same spirit of the previous comment, I would call here response.closeOutput(Callback.from(_state::completed)) rather than doing the setter/getter dance.
| while (tryExecute(NOOP)) | ||
| { | ||
| } | ||
|
|
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.
What's the purpose of this?
ReservedThreadExecutor may accept STOP tasks, but the problem is that few lines above _tryExecutor has been replaced with NO_TRY so isn't this just a no-op?
jetty-server/src/main/java/org/eclipse/jetty/server/handler/ErrorHandler.java
Show resolved
Hide resolved
joakime
left a comment
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 have no additional changes requested over what @sbordet is asking for.
Other then that, I'm good.
Signed-off-by: Greg Wilkins <[email protected]>
sbordet
left a comment
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.
Sorry, still not happy about the close() method.
| _closeCallback = null; | ||
| if (closeCallback == null) | ||
| closeCallback = BLOCKING_CLOSE_CALLBACK; | ||
| Callback closeCallback = _closeCallback == null ? BLOCKING_CLOSE_CALLBACK : _closeCallback; |
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.
Uhm. Now that you have a close(Callback) method, this method can be implemented in terms of the other.
public void close()
{
FutureCallback callback = new FutureCallback();
close(callback);
callback.await();
}The other method does all the lifting.
My point being that we should avoid having the _closeCallback field and continuously test for it being non-null and completing it. It should just be a local variable in the blocking version of the close() method.
}
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.
@sbordet sorry but it can't be done this way. The new close method is passed a closeable, which either be null or the Writer used for the output. The writer is closed first and if all goes OK, it will be the writer that calls HttpOutput.close(), so there is no possibility to pass the callback through the writer. Thus a field is really the simplest way to capture this.
…06-async-sendError-fullyAsync Signed-off-by: Greg Wilkins <[email protected]>
Simone is on vacation and his last remaining issue cannot be resolved in the way he would like.
|
is this fix in a snapshot release? |
|
@vickumar1981 it will be available in the upcoming 9.4.21 release. <repositories>
<repository>
<id>jetty.snapshot</id>
<url>https://oss.sonatype.org/content/repositories/jetty-snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories> |
This PR for #3806 builds on #3850 with even more "adventurous" improvements.
HttpChannelState in particular has been significantly refactored (hopefully simplified) so that sendError, close and final flushes can be done asynchronously