Fix #5605 write side
refactored the complete method to consider unrecoverable API states no matter what the httpout state actually is. This avoid duplication of OPEN, CLOSING, CLOSED etc. handling.
This commit is contained in:
parent
ed534b84ef
commit
9f2a4f5ad5
|
@ -412,65 +412,87 @@ public class HttpOutput extends ServletOutputStream implements Runnable
|
|||
ByteBuffer content = null;
|
||||
synchronized (_channelState)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -114,7 +114,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