Merge remote-tracking branch 'origin/jetty-9.4.x-5605-wakeup-blocked-threads' into jetty-10.0.x-5605-wakeup-blocked-threads

This commit is contained in:
Ludovic Orban 2021-02-10 15:53:38 +01:00
commit 03e2789699
2 changed files with 70 additions and 47 deletions

View File

@ -397,6 +397,37 @@ public class HttpOutput extends ServletOutputStream implements Runnable
ByteBuffer content = null; ByteBuffer content = null;
try (AutoLock l = _channelState.lock()) try (AutoLock l = _channelState.lock())
{ {
// First check the API state for any unrecoverable situations
switch (_apiState)
{
case UNREADY: // isReady() has returned false so a call to onWritePossible may happen at any time
error = new CancellationException("Completed whilst write unready");
break;
case PENDING: // an async write is pending and may complete at any time
// If this is not the last write, then we must abort
if (!_channel.getResponse().isContentComplete(_written))
error = new CancellationException("Completed whilst write pending");
break;
case BLOCKED: // another thread is blocked in a write or a close
error = new CancellationException("Completed whilst write blocked");
break;
default:
break;
}
// If we can't complete due to the API state, then abort
if (error != null)
{
_writeBlocker.fail(error);
_channel.abort(error);
_state = State.CLOSED;
}
else
{
// Otherwise check the output state to determine how to complete
switch (_state) switch (_state)
{ {
case CLOSED: case CLOSED:
@ -434,30 +465,21 @@ public class HttpOutput extends ServletOutputStream implements Runnable
content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER; content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
break; break;
case BLOCKED:
case UNREADY: case UNREADY:
case PENDING: case PENDING:
LOG.warn("Pending write in complete {} {}", this, _channel);
// An operation is in progress, so we soft close now // An operation is in progress, so we soft close now
_softClose = true; _softClose = true;
// then trigger a close from onWriteComplete // then trigger a close from onWriteComplete
_state = State.CLOSE; _state = State.CLOSE;
// But if we are blocked or there is more content to come, we must abort
// Note that this allows a pending async write to complete only if it is the last write
if (_apiState == ApiState.BLOCKED || !_channel.getResponse().isContentComplete(_written))
{
CancellationException cancelled = new CancellationException();
_writeBlocker.fail(cancelled);
_channel.abort(cancelled);
_state = State.CLOSED;
}
break; break;
default:
throw new IllegalStateException();
} }
break; break;
} }
} }
}
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("complete({}) {} s={} e={}, c={}", callback, stateString(), succeeded, error, BufferUtil.toDetailString(content)); LOG.debug("complete({}) {} s={} e={}, c={}", callback, stateString(), succeeded, error, BufferUtil.toDetailString(content));

View File

@ -109,7 +109,8 @@ public class BlockedIOTest extends AbstractTest<TransportScenario>
DeferredContentProvider contentProvider = new DeferredContentProvider(); DeferredContentProvider contentProvider = new DeferredContentProvider();
CountDownLatch ok = new CountDownLatch(2); CountDownLatch ok = new CountDownLatch(2);
scenario.client.POST(scenario.newURI()) scenario.client.newRequest(scenario.newURI())
.method("POST")
.content(contentProvider) .content(contentProvider)
.onResponseContent((response, content) -> .onResponseContent((response, content) ->
{ {