Skip to content

Conversation

@gregw
Copy link
Contributor

@gregw gregw commented Jul 29, 2019

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

gregw added 30 commits July 3, 2019 12:36
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]>
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]>
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]>
@sbordet
Copy link
Contributor

sbordet commented Aug 8, 2019

@janbartel has pointed out that we have the following pattern in a number of places...

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 AsyncContext.complete() so that now there is a race with request.isAsyncStarted() and possibly throwing an IllegalStateException when calling request.getAsyncContext().

If the handling throws an exception, we will first go to the finally block and then we will do the ERROR dispatch, so don't think it's a problem.

If the handling calls sendError() we will just change the state and only when we exit the finally block we would do the ERROR dispatch, no?

I think the sync case is ok, it's the async case that is racy.

@sbordet
Copy link
Contributor

sbordet commented Aug 8, 2019

@gregw so I have doubts about the broken pattern that you found.
As I see more commits after the joined review we did, should we re-review it? Or more commits are coming?

@gregw
Copy link
Contributor Author

gregw commented Aug 8, 2019

@sbordet happy to do another review of this.... but the pattern was not racy....

Only a dispatched thread can call startAsync so that is not a race.
Once async, another thread might call complete at any time, but that complete call does not take affect until after the original dispatch has returned. Thus the dispatch thread is still able to add listeners to the AsyncContext (otherwise all listeners would be in a race).

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 completeSomething(), in the case of sessions, that might reduce the reference count on the session to 0, so the session is passivated and made non resident. An error page dispatch is then done and the session has to be retrieved again.... but it will now the SessionHandler that used to think that ERROR dispatches could not be for non-resident sessions has to be aware that the session might not be resident and load it, inc reference count etc.

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.
Using HttpChannel.Listener in test harnesses to wait for completion is a good way to avoid this pattern for non production code....

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.....

@gregw
Copy link
Contributor Author

gregw commented Aug 13, 2019

I think we need to merge this soon to avoid it going stale.

@gregw gregw requested review from joakime and sbordet August 13, 2019 00:05
gregw added 2 commits August 21, 2019 12:06
…06-async-sendError-fullyAsync

Signed-off-by: Greg Wilkins <[email protected]>
Signed-off-by: Greg Wilkins <[email protected]>
public Callback getClosedCallback()
{
return _closeCallback;
}
Copy link
Contributor

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.

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 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();
Copy link
Contributor

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))
{
}

Copy link
Contributor

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?

@joakime joakime added the Bug For general bugs on Jetty side label Aug 21, 2019
@joakime joakime changed the title Jetty 9.4.x 3806 async send error fully async Issue #3806 - Make Async sendError fully Async Aug 21, 2019
Copy link
Contributor

@joakime joakime left a 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]>
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.

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;
Copy link
Contributor

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.
}

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 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]>
@gregw gregw dismissed sbordet’s stale review August 26, 2019 07:50

Simone is on vacation and his last remaining issue cannot be resolved in the way he would like.

@gregw gregw merged commit bde8646 into jetty-9.4.x Aug 26, 2019
@gregw gregw deleted the jetty-9.4.x-3806-async-sendError-fullyAsync branch August 26, 2019 07:56
@vickumar1981
Copy link

is this fix in a snapshot release?

@joakime
Copy link
Contributor

joakime commented Sep 17, 2019

@vickumar1981 it will be available in the upcoming 9.4.21 release.
It is also available as a 9.4.21-SNAPSHOT at the official Jetty snapshot repository.

<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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bug For general bugs on Jetty side

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants