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,65 +397,87 @@ public class HttpOutput extends ServletOutputStream implements Runnable
ByteBuffer content = null;
try (AutoLock l = _channelState.lock())
{
switch (_state)
// First check the API state for any unrecoverable situations
switch (_apiState)
{
case CLOSED:
succeeded = true;
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 CLOSE:
case CLOSING:
_closedCallback = Callback.combine(_closedCallback, callback);
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 OPEN:
if (_onError != null)
{
error = _onError;
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)
{
case CLOSED:
succeeded = true;
break;
}
_closedCallback = Callback.combine(_closedCallback, callback);
case CLOSE:
case CLOSING:
_closedCallback = Callback.combine(_closedCallback, callback);
break;
switch (_apiState)
{
case BLOCKING:
// Output is idle blocking state, but we still do an async close
_apiState = ApiState.BLOCKED;
_state = State.CLOSING;
content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
case OPEN:
if (_onError != null)
{
error = _onError;
break;
}
case ASYNC:
case READY:
// Output is idle in async state, so we can do an async close
_apiState = ApiState.PENDING;
_state = State.CLOSING;
content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
break;
_closedCallback = Callback.combine(_closedCallback, callback);
case BLOCKED:
case UNREADY:
case PENDING:
LOG.warn("Pending write in complete {} {}", this, _channel);
// An operation is in progress, so we soft close now
_softClose = true;
// then trigger a close from onWriteComplete
_state = State.CLOSE;
switch (_apiState)
{
case BLOCKING:
// Output is idle blocking state, but we still do an async close
_apiState = ApiState.BLOCKED;
_state = State.CLOSING;
content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
break;
// 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;
}
case ASYNC:
case READY:
// Output is idle in async state, so we can do an async close
_apiState = ApiState.PENDING;
_state = State.CLOSING;
content = BufferUtil.hasContent(_aggregate) ? _aggregate : BufferUtil.EMPTY_BUFFER;
break;
break;
}
break;
case UNREADY:
case PENDING:
// An operation is in progress, so we soft close now
_softClose = true;
// then trigger a close from onWriteComplete
_state = State.CLOSE;
break;
default:
throw new IllegalStateException();
}
break;
}
}
}

View File

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