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:
gregw 2021-02-10 15:35:48 +01:00
parent ed534b84ef
commit 9f2a4f5ad5
2 changed files with 70 additions and 47 deletions

View File

@ -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;
}
}
}

View File

@ -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) ->
{