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:
commit
03e2789699
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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) ->
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue