Issue #6728 - QUIC and HTTP/3

- Made BlockingContentProducer.onContentProducible() idempotent by checking if the input
is unready before releasing the semaphore.
This is necessary because HTTP/3 will call onContentProducible() just after receiving the request,
while other protocols assume that content is producible and only call onContentProducible()
when they read all the available content.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-10-28 11:29:01 +02:00
parent baab1a15b9
commit 23111b744f
3 changed files with 23 additions and 8 deletions

View File

@ -312,6 +312,11 @@ class AsyncContentProducer implements ContentProducer
return false;
}
boolean isUnready()
{
return _httpChannel.getState().isInputUnready();
}
private HttpInput.Content nextTransformedContent()
{
if (LOG.isDebugEnabled())

View File

@ -160,17 +160,19 @@ class BlockingContentProducer implements ContentProducer
{
_semaphore.assertLocked();
// In blocking mode, the dispatched thread normally does not have to be rescheduled as it is normally in state
// DISPATCHED blocked on the semaphore that just needs to be released for the dispatched thread to resume. This is why
// this method always returns false.
// DISPATCHED blocked on the semaphore that just needs to be released for the dispatched thread to resume.
// This is why this method always returns false.
// But async errors can occur while the dispatched thread is NOT blocked reading (i.e.: in state WAITING),
// so the WAITING to WOKEN transition must be done by the error-notifying thread which then has to reschedule the
// dispatched thread after HttpChannelState.asyncError() is called.
// Calling _asyncContentProducer.wakeup() changes the channel state from WAITING to WOKEN which would prevent the
// subsequent call to HttpChannelState.asyncError() from rescheduling the thread.
// so the WAITING to WOKEN transition must be done by the error-notifying thread which then has to reschedule
// the dispatched thread after HttpChannelState.asyncError() is called.
// Calling _asyncContentProducer.onContentProducible() changes the channel state from WAITING to WOKEN which
// would prevent the subsequent call to HttpChannelState.asyncError() from rescheduling the thread.
// AsyncServletTest.testStartAsyncThenClientStreamIdleTimeout() tests this.
boolean unready = _asyncContentProducer.isUnready();
if (LOG.isDebugEnabled())
LOG.debug("onContentProducible releasing semaphore {}", _semaphore);
_semaphore.release();
LOG.debug("onContentProducible releasing semaphore {} unready={}", _semaphore, unready);
if (unready)
_semaphore.release();
return false;
}
}

View File

@ -1361,6 +1361,14 @@ public class HttpChannelState
}
}
public boolean isInputUnready()
{
try (AutoLock l = lock())
{
return _inputState == InputState.UNREADY;
}
}
public boolean onWritePossible()
{
boolean wake = false;