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

Issue #8211 - fix ISE from HttpChannelState if failure during process #8215

Merged
merged 9 commits into from
Aug 4, 2022

Conversation

lachlan-roberts
Copy link
Contributor

If a timeout or stream reset occurs during process, the HttpChannelState will be failed, this causes callback.succeeded() to throw ISE. This ISE is then caught in HandlerInvoker and the callback is failed, and because the callback was failed twice we get the java.lang.IllegalStateException: already completed seen in #8211.

This PR fixes this by

  • only failing the callback in HandlerInvoker if HttpChannel has not been completed.
  • Do not throw from callback.failed() if the callback is failed with same cause as why the HttpChannel was failed.

@lachlan-roberts lachlan-roberts requested review from gregw and sbordet June 28, 2022 06:39
Copy link
Contributor

@gregw gregw left a comment

Choose a reason for hiding this comment

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

I think this LG, but as I'm kind of a co-author, we should get @sbordet's thoughts as well.

Comment on lines 666 to 674
{
try (AutoLock ignored = _lock.lock())
{
if (request._httpChannel != null && !request._httpChannel._completed)
request._callback.failed(failure);
else if (LOG.isDebugEnabled())
LOG.debug("Process failed", failure);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Potentially it would be neater to put this logic into a method:

Suggested change
{
try (AutoLock ignored = _lock.lock())
{
if (request._httpChannel != null && !request._httpChannel._completed)
request._callback.failed(failure);
else if (LOG.isDebugEnabled())
LOG.debug("Process failed", failure);
}
}
request._callback.ensureCompleted(failure);

gregw
gregw previously requested changes Jun 29, 2022
Copy link
Contributor

@gregw gregw left a comment

Choose a reason for hiding this comment

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

oops I approved when I wanted to request some small changes.

Signed-off-by: Lachlan Roberts <[email protected]>
@gregw gregw dismissed their stale review July 4, 2022 05:32

LGTM, but as a coauther will defer to others to approve

@joakime joakime added this to the 12.0.x milestone Aug 2, 2022
@lachlan-roberts lachlan-roberts requested a review from gregw August 3, 2022 22:01
sbordet added 2 commits August 4, 2022 12:09
…ready HttpChannelState._completed.

Now all the logic for completion is in lockedOnComplete().

Avoid throwing in ChannelCallback.succeeded() if HttpChannelState._error != null.
This is necessary because HttpChannelState._error may be set asynchronously by
some event such as HTTP/2 reset streams or idle timeouts, but if there is a
thread dispatched to the application the asynchronous event will not fail the
callback, as the failure may be noticed by the application (e.g. via a read() call).

Fixed TrailersTest.testHandlerRequestTrailers() by avoid reading again after
having read the trailers.

Signed-off-by: Simone Bordet <[email protected]>
@Test
public void testForgivingFailedClientEnforcingStreamIdleTimeout() throws Exception
{
final int idleTimeout = 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

No need for final, here and below.

@@ -1317,6 +1319,10 @@ public void succeeded()
boolean completeStream;
try (AutoLock ignored = _request._lock.lock())
{
if (_completed)
return;
_completed = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

I would not do this check here.
The call below lockedOnComplete() is the one responsible for setting _completed to true, and throwing if it's already set.
I would: A) make lockedOnComplete() return a boolean, and if it is false return; and B) remove the code in lockedOnComplete() that throws, which is now never executed.

@@ -1377,6 +1383,10 @@ public void failed(Throwable failure)
boolean completeStream;
try (AutoLock ignored = _request._lock.lock())
{
if (_completed)
return;
_completed = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Ditto.

if (failure != null)
request._callback.failed(failure);
_request._callback.ensureCompleted(failure);
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not sure I understand this. With the current modifications, the original code calling failed() is already doing the check on _completed, so this method does nothing different, and certainly does not "ensure" and it only fails the callback, while the name "ensureCompleted" seems to hint that it either succeeds or fails it.
I would restore the old code.

@sbordet
Copy link
Contributor

sbordet commented Aug 4, 2022

@lachlan-roberts I have pushed changes that align to my comments, so I'll merge this PR once build passes.

@sbordet sbordet merged commit e086cab into jetty-12.0.x Aug 4, 2022
@sbordet sbordet deleted the jetty-12.0.x-8211-channelAlreadyComplete branch August 4, 2022 15:06
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