Issue #6728 - QUIC and HTTP/3

- Improved close mechanism.
Now error and reason are propagated at the HTTP/3 level, in case e.g. applications want to take statistics about the error codes.
- Improved buffer handling to be sure they are properly released back to the pool.

Signed-off-by: Simone Bordet <simone.bordet@gmail.com>
This commit is contained in:
Simone Bordet 2021-11-01 17:45:03 +01:00
parent 85a13cfc20
commit 521a0adf0e
18 changed files with 269 additions and 144 deletions

View File

@ -166,7 +166,7 @@ public class ClientHTTP3Session extends ClientProtocolSession
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("inward closing 0x{}/{} on {}", Long.toHexString(error), reason, this); LOG.debug("inward closing 0x{}/{} on {}", Long.toHexString(error), reason, this);
session.disconnect(reason); session.inwardClose(error, reason);
} }
@Override @Override

View File

@ -188,8 +188,10 @@ public interface Session
* <p>Callback method invoked when the underlying transport has been disconnected.</p> * <p>Callback method invoked when the underlying transport has been disconnected.</p>
* *
* @param session the session * @param session the session
* @param error the disconnect error
* @param reason the disconnect reason
*/ */
public default void onDisconnect(Session session) public default void onDisconnect(Session session, long error, String reason)
{ {
} }
@ -236,9 +238,10 @@ public interface Session
* <p>Callback method invoked when a failure has been detected for this session.</p> * <p>Callback method invoked when a failure has been detected for this session.</p>
* *
* @param session the session * @param session the session
* @param failure the cause of the failure * @param error the failure error
* @param reason the failure reason
*/ */
public default void onFailure(Session session, Throwable failure) public default void onFailure(Session session, long error, String reason)
{ {
} }
} }

View File

@ -237,9 +237,10 @@ public interface Stream
* the stream has been reset.</p> * the stream has been reset.</p>
* *
* @param stream the stream * @param stream the stream
* @param error the failure error
* @param failure the cause of the failure * @param failure the cause of the failure
*/ */
public default void onFailure(Stream stream, Throwable failure) public default void onFailure(Stream stream, long error, Throwable failure)
{ {
} }
} }

View File

@ -183,7 +183,7 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
else else
{ {
closeState = CloseState.CLOSING; closeState = CloseState.CLOSING;
zeroStreamsAction = () -> terminate("go_away"); zeroStreamsAction = this::terminate;
} }
} }
break; break;
@ -212,7 +212,13 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
else else
{ {
if (failStreams) if (failStreams)
failStreams(stream -> true, "go_away", true); {
long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code();
String reason = "go_away";
failStreams(stream -> true, error, reason, true);
terminate();
outwardDisconnect(error, reason);
}
return CompletableFuture.completedFuture(null); return CompletableFuture.completedFuture(null);
} }
} }
@ -240,13 +246,6 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
Atomics.updateMax(lastId, id); Atomics.updateMax(lastId, id);
} }
public void outwardClose(long error, String reason)
{
if (LOG.isDebugEnabled())
LOG.debug("outward closing 0x{}/{} on {}", Long.toHexString(error), reason, this);
getProtocolSession().outwardClose(error, reason);
}
public long getIdleTimeout() public long getIdleTimeout()
{ {
return getProtocolSession().getIdleTimeout(); return getProtocolSession().getIdleTimeout();
@ -475,7 +474,7 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
if (stream != null) if (stream != null)
stream.onData(frame); stream.onData(frame);
else else
fail(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence"); onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence");
} }
public void onDataAvailable(long streamId) public void onDataAvailable(long streamId)
@ -486,17 +485,6 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
stream.onDataAvailable(); stream.onDataAvailable();
} }
void fail(long error, String reason)
{
// Hard failure, no need to send a GOAWAY.
try (AutoLock l = lock.lock())
{
closeState = CloseState.CLOSED;
}
outwardClose(error, reason);
notifyFailure(new IOException(String.format("%d/%s", error, reason)));
}
@Override @Override
public void onGoAway(GoAwayFrame frame) public void onGoAway(GoAwayFrame frame)
{ {
@ -522,7 +510,7 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
goAwaySent = newGoAwayFrame(false); goAwaySent = newGoAwayFrame(false);
closeState = CloseState.CLOSING; closeState = CloseState.CLOSING;
GoAwayFrame goAwayFrame = goAwaySent; GoAwayFrame goAwayFrame = goAwaySent;
zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(() -> terminate("go_away"))); zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(this::terminate));
failStreams = true; failStreams = true;
} }
break; break;
@ -542,11 +530,19 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
{ {
goAwaySent = newGoAwayFrame(false); goAwaySent = newGoAwayFrame(false);
GoAwayFrame goAwayFrame = goAwaySent; GoAwayFrame goAwayFrame = goAwaySent;
zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(() -> terminate("go_away"))); zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(() ->
{
terminate();
outwardDisconnect(HTTP3ErrorCode.NO_ERROR.code(), "go_away");
}));
} }
else else
{ {
zeroStreamsAction = () -> terminate("go_away"); zeroStreamsAction = () ->
{
terminate();
outwardDisconnect(HTTP3ErrorCode.NO_ERROR.code(), "go_away");
};
failStreams = true; failStreams = true;
} }
} }
@ -567,11 +563,11 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
{ {
goAwaySent = newGoAwayFrame(false); goAwaySent = newGoAwayFrame(false);
GoAwayFrame goAwayFrame = goAwaySent; GoAwayFrame goAwayFrame = goAwaySent;
zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(() -> terminate("go_away"))); zeroStreamsAction = () -> writeControlFrame(goAwayFrame, Callback.from(this::terminate));
} }
else else
{ {
zeroStreamsAction = () -> terminate("go_away"); zeroStreamsAction = this::terminate;
} }
failStreams = true; failStreams = true;
} }
@ -598,7 +594,7 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
// The other peer sent us a GOAWAY with the last processed streamId, // The other peer sent us a GOAWAY with the last processed streamId,
// so we must fail the streams that have a bigger streamId. // so we must fail the streams that have a bigger streamId.
Predicate<HTTP3Stream> predicate = stream -> stream.isLocal() && stream.getId() > frame.getLastId(); Predicate<HTTP3Stream> predicate = stream -> stream.isLocal() && stream.getId() > frame.getLastId();
failStreams(predicate, "go_away", true); failStreams(predicate, HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code(), "go_away", true);
} }
tryRunZeroStreamsAction(); tryRunZeroStreamsAction();
@ -645,12 +641,22 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
if (!confirmed) if (!confirmed)
return false; return false;
disconnect("idle_timeout"); inwardClose(HTTP3ErrorCode.NO_ERROR.code(), "idle_timeout");
return false; return false;
} }
public void disconnect(String reason) /**
* <p>Called when a an external event wants to initiate the close of this session locally,
* for example a close at the network level (due to e.g. stopping a component) or a timeout.</p>
* <p>The correspondent passive event, where it's the remote peer that initiates the close,
* is delivered via {@link #onClose(long, String)}.</p>
*
* @param error the close error
* @param reason the close reason
* @see #onClose(long, String)
*/
public void inwardClose(long error, String reason)
{ {
GoAwayFrame goAwayFrame = null; GoAwayFrame goAwayFrame = null;
try (AutoLock l = lock.lock()) try (AutoLock l = lock.lock())
@ -678,17 +684,58 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
} }
} }
failStreams(stream -> true, reason, true); failStreams(stream -> true, error, reason, true);
if (goAwayFrame != null) if (goAwayFrame != null)
writeControlFrame(goAwayFrame, Callback.from(() -> terminate(reason))); {
writeControlFrame(goAwayFrame, Callback.from(() ->
{
terminate();
outwardDisconnect(error, reason);
}));
}
else else
terminate(reason); {
terminate();
outwardDisconnect(error, reason);
}
} }
private void failStreams(Predicate<HTTP3Stream> predicate, String reason, boolean close) /**
* <p>Calls {@link #outwardClose(long, String)}, then notifies
* {@link Session.Listener#onDisconnect(Session, long, String)}.</p>
*
* @param error the close error
* @param reason the close reason.
* @see #outwardClose(long, String)
*/
private void outwardDisconnect(long error, String reason)
{
outwardClose(error, reason);
// Since the outwardClose() above is called by
// the implementation, notify the application.
notifyDisconnect(error, reason);
}
/**
* <p>Propagates a close outwards, i.e. towards the network.</p>
* <p>This method does not notify {@link Session.Listener#onDisconnect(Session, long, String)}
* so calling {@link #outwardDisconnect(long, String)} is preferred.</p>
*
* @param error the close error
* @param reason the close reason
* @see #outwardDisconnect(long, String)
* @see #inwardClose(long, String)
*/
private void outwardClose(long error, String reason)
{
if (LOG.isDebugEnabled())
LOG.debug("outward closing 0x{}/{} on {}", Long.toHexString(error), reason, this);
getProtocolSession().outwardClose(error, reason);
}
private void failStreams(Predicate<HTTP3Stream> predicate, long error, String reason, boolean close)
{ {
long error = HTTP3ErrorCode.REQUEST_CANCELLED_ERROR.code();
Throwable failure = new IOException(reason); Throwable failure = new IOException(reason);
streams.values().stream() streams.values().stream()
.filter(predicate) .filter(predicate)
@ -698,19 +745,22 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
stream.reset(error, failure); stream.reset(error, failure);
// Since the stream failure was generated // Since the stream failure was generated
// by a GOAWAY, notify the application. // by a GOAWAY, notify the application.
stream.onFailure(failure); stream.onFailure(error, failure);
}); });
} }
private void terminate(String reason) /**
* Terminates this session at the HTTP/3 level, and possibly notifies the shutdown callback.
* Termination at the QUIC level may still be in progress.
*
* @see #onClose(long, String)
* @see #inwardClose(long, String)
*/
private void terminate()
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("terminating reason={} for {}", reason, this); LOG.debug("terminating {}", this);
streamTimeouts.destroy(); streamTimeouts.destroy();
outwardClose(HTTP3ErrorCode.NO_ERROR.code(), reason);
// Since the close() above is called by the
// implementation, notify the application.
notifyDisconnect();
// Notify the shutdown completable. // Notify the shutdown completable.
CompletableFuture<Void> shutdown; CompletableFuture<Void> shutdown;
try (AutoLock l = lock.lock()) try (AutoLock l = lock.lock())
@ -781,6 +831,14 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
} }
} }
/**
* <p>Called when the local peer receives a close initiated by the remote peer.</p>
* <p>The correspondent active event, where it's the local peer that initiates the close,
* it's delivered via {@link #inwardClose(long, String)}.</p>
*
* @param error the close error
* @param reason the close reason
*/
public void onClose(long error, String reason) public void onClose(long error, String reason)
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
@ -797,19 +855,19 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
} }
// No point in closing the streams, as QUIC frames cannot be sent. // No point in closing the streams, as QUIC frames cannot be sent.
failStreams(stream -> true, "remote_close", false); failStreams(stream -> true, error, reason, false);
if (notifyFailure) if (notifyFailure)
fail(error, reason); onSessionFailure(error, reason);
notifyDisconnect(); notifyDisconnect(error, reason);
} }
private void notifyDisconnect() private void notifyDisconnect(long error, String reason)
{ {
try try
{ {
listener.onDisconnect(this); listener.onDisconnect(this, error, reason);
} }
catch (Throwable x) catch (Throwable x)
{ {
@ -824,24 +882,21 @@ public abstract class HTTP3Session extends ContainerLifeCycle implements Session
LOG.debug("stream failure 0x{}/{} for stream #{} on {}", Long.toHexString(error), failure.getMessage(), streamId, this); LOG.debug("stream failure 0x{}/{} for stream #{} on {}", Long.toHexString(error), failure.getMessage(), streamId, this);
HTTP3Stream stream = getStream(streamId); HTTP3Stream stream = getStream(streamId);
if (stream != null) if (stream != null)
{ stream.onFailure(error, failure);
stream.onFailure(failure);
removeStream(stream, failure);
}
} }
@Override @Override
public void onSessionFailure(long error, String reason) public void onSessionFailure(long error, String reason)
{ {
// TODO notifyFailure(error, reason);
throw new UnsupportedOperationException(); inwardClose(error, reason);
} }
public void notifyFailure(Throwable failure) private void notifyFailure(long error, String reason)
{ {
try try
{ {
listener.onFailure(this, failure); listener.onFailure(this, error, reason);
} }
catch (Throwable x) catch (Throwable x)
{ {

View File

@ -327,19 +327,19 @@ public class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, Attachable
} }
} }
public void onFailure(Throwable failure) public void onFailure(long error, Throwable failure)
{ {
notifyFailure(failure); notifyFailure(error, failure);
session.removeStream(this, failure); session.removeStream(this, failure);
} }
private void notifyFailure(Throwable failure) private void notifyFailure(long error, Throwable failure)
{ {
Listener listener = getListener(); Listener listener = getListener();
try try
{ {
if (listener != null) if (listener != null)
listener.onFailure(this, failure); listener.onFailure(this, error, failure);
} }
catch (Throwable x) catch (Throwable x)
{ {
@ -361,7 +361,7 @@ public class HTTP3Stream implements Stream, CyclicTimeouts.Expirable, Attachable
if (frameState == FrameState.FAILED) if (frameState == FrameState.FAILED)
return false; return false;
frameState = FrameState.FAILED; frameState = FrameState.FAILED;
session.fail(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence"); session.onSessionFailure(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), "invalid_frame_sequence");
return false; return false;
} }
} }

View File

@ -202,7 +202,12 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("read data {} on {}", frame, this); LOG.debug("read data {} on {}", frame, this);
buffer.retain(); buffer.retain();
return new Stream.Data(frame, this::completeReadData); // Store in a local variable so that the lambda captures the right buffer.
RetainableByteBuffer current = buffer;
// Release the network buffer here (if empty), since the application may
// not be reading more bytes, to avoid to keep around a consumed buffer.
tryReleaseBuffer(false);
return new Stream.Data(frame, () -> completeReadData(current));
} }
case MODE_SWITCH: case MODE_SWITCH:
{ {
@ -237,11 +242,11 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
} }
} }
private void completeReadData() private void completeReadData(RetainableByteBuffer buffer)
{ {
buffer.release(); buffer.release();
if (!buffer.isRetained()) if (LOG.isDebugEnabled())
tryReleaseBuffer(false); LOG.debug("retained released {}", buffer);
} }
public void demand() public void demand()
@ -377,7 +382,10 @@ public abstract class HTTP3StreamConnection extends AbstractConnection
if (buffer.isRetained()) if (buffer.isRetained())
{ {
buffer.release(); buffer.release();
buffer = buffers.acquire(getInputBufferSize(), isUseInputDirectByteBuffers()); RetainableByteBuffer newBuffer = buffers.acquire(getInputBufferSize(), isUseInputDirectByteBuffers());
if (LOG.isDebugEnabled())
LOG.debug("reacquired {} for retained {}", newBuffer, buffer);
buffer = newBuffer;
byteBuffer = buffer.getBuffer(); byteBuffer = buffer.getBuffer();
} }

View File

@ -186,7 +186,7 @@ public class HttpReceiverOverHTTP3 extends HttpReceiver implements Stream.Listen
} }
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
responseFailure(failure); responseFailure(failure);
} }

View File

@ -13,7 +13,7 @@
package org.eclipse.jetty.http3.client.http.internal; package org.eclipse.jetty.http3.client.http.internal;
import java.nio.channels.ClosedChannelException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicMarkableReference; import java.util.concurrent.atomic.AtomicMarkableReference;
@ -66,14 +66,15 @@ public class SessionClientListener implements Session.Client.Listener
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
onFailure(session, new ClosedChannelException()); onFailure(session, error, reason);
} }
@Override @Override
public void onFailure(Session session, Throwable failure) public void onFailure(Session session, long error, String reason)
{ {
IOException failure = new IOException(String.format("%#x/%s", error, reason));
if (failConnectionPromise(failure)) if (failConnectionPromise(failure))
return; return;
HttpConnectionOverHTTP3 connection = this.connection.getReference(); HttpConnectionOverHTTP3 connection = this.connection.getReference();

View File

@ -13,6 +13,7 @@
package org.eclipse.jetty.http3.server; package org.eclipse.jetty.http3.server;
import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.api.Session;
@ -67,10 +68,12 @@ public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionF
} }
@Override @Override
public void onFailure(Session session, Throwable failure) public void onFailure(Session session, long error, String reason)
{ {
// TODO IOException failure = new IOException(reason);
throw new UnsupportedOperationException(); session.getStreams().stream()
.map(stream -> (HTTP3Stream)stream)
.forEach(stream -> stream.onFailure(error, failure));
} }
} }
@ -138,9 +141,15 @@ public class HTTP3ServerConnectionFactory extends AbstractHTTP3ServerConnectionF
} }
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
getConnection().onFailure((HTTP3Stream)stream, failure); HTTP3Stream http3Stream = (HTTP3Stream)stream;
Runnable task = getConnection().onFailure((HTTP3Stream)stream, failure);
if (task != null)
{
ServerHTTP3Session protocolSession = (ServerHTTP3Session)http3Stream.getSession().getProtocolSession();
protocolSession.offer(task, true);
}
} }
} }
} }

View File

@ -244,7 +244,7 @@ public class HttpChannelOverHTTP3 extends HttpChannel
if (reset) if (reset)
consumeInput(); consumeInput();
getHttpTransport().onStreamIdleTimeout(failure); getHttpTransport().onIdleTimeout(failure);
failure.addSuppressed(new Throwable("idle timeout")); failure.addSuppressed(new Throwable("idle timeout"));
@ -267,14 +267,21 @@ public class HttpChannelOverHTTP3 extends HttpChannel
handle(); handle();
} }
public void onFailure(Throwable failure) public Runnable onFailure(Throwable failure)
{ {
//TODO consumeInput();
throw new UnsupportedOperationException(failure);
// getHttpTransport().onStreamFailure(failure); getHttpTransport().onFailure(failure);
// boolean handle = failed(failure);
// consumeInput(); boolean handle = failed(failure);
// return new FailureTask(failure, callback, handle);
return () ->
{
if (handle)
handleWithContext();
else if (getHttpConfiguration().isNotifyRemoteAsyncErrors())
getState().asyncError(failure);
};
} }
@Override @Override
@ -390,8 +397,31 @@ public class HttpChannelOverHTTP3 extends HttpChannel
@Override @Override
public boolean failed(Throwable failure) public boolean failed(Throwable failure)
{ {
// TODO HttpInput.Content contentToFail = null;
throw new UnsupportedOperationException(failure); try (AutoLock l = lock.lock())
{
if (content == null)
{
content = new HttpInput.ErrorContent(failure);
}
else
{
if (content.isSpecial())
{
// Either EOF or error already, no nothing.
}
else
{
contentToFail = content;
content = new HttpInput.ErrorContent(failure);
}
}
}
if (contentToFail != null)
contentToFail.failed(failure);
return getRequest().getHttpInput().onContentProducible();
} }
@Override @Override

View File

@ -278,11 +278,16 @@ public class HttpTransportOverHTTP3 implements HttpTransport
} }
} }
boolean onStreamIdleTimeout(Throwable failure) boolean onIdleTimeout(Throwable failure)
{ {
return transportCallback.idleTimeout(failure); return transportCallback.idleTimeout(failure);
} }
void onFailure(Throwable failure)
{
transportCallback.abort(failure);
}
@Override @Override
public void abort(Throwable failure) public void abort(Throwable failure)
{ {

View File

@ -158,7 +158,7 @@ public class ServerHTTP3Session extends ServerProtocolSession
{ {
if (LOG.isDebugEnabled()) if (LOG.isDebugEnabled())
LOG.debug("inward closing 0x{}/{} on {}", Long.toHexString(error), reason, this); LOG.debug("inward closing 0x{}/{} on {}", Long.toHexString(error), reason, this);
session.disconnect(reason); session.inwardClose(error, reason);
} }
@Override @Override

View File

@ -69,9 +69,9 @@ public class ServerHTTP3StreamConnection extends HTTP3StreamConnection
return channel.onIdleTimeout(failure, consumer); return channel.onIdleTimeout(failure, consumer);
} }
public void onFailure(HTTP3Stream stream, Throwable failure) public Runnable onFailure(HTTP3Stream stream, Throwable failure)
{ {
HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment(); HttpChannelOverHTTP3 channel = (HttpChannelOverHTTP3)stream.getAttachment();
channel.onFailure(failure); return channel.onFailure(failure);
} }
} }

View File

@ -393,7 +393,7 @@ public class ClientServerTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/large"), true), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/large"), true), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
streamFailureLatch.countDown(); streamFailureLatch.countDown();
} }

View File

@ -73,7 +73,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -90,7 +90,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -153,7 +153,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -170,7 +170,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -190,7 +190,7 @@ public class GoAwayTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/2"), true), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/2"), true), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
streamFailureLatch.countDown(); streamFailureLatch.countDown();
} }
@ -232,7 +232,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -253,7 +253,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -315,7 +315,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -336,7 +336,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -405,7 +405,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -422,7 +422,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -492,7 +492,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -517,7 +517,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -607,7 +607,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -628,7 +628,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -673,7 +673,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -689,7 +689,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -723,7 +723,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -746,7 +746,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -787,7 +787,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -806,7 +806,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -849,7 +849,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -868,7 +868,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -916,7 +916,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -938,7 +938,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -948,7 +948,7 @@ public class GoAwayTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
clientFailureLatch.countDown(); clientFailureLatch.countDown();
} }
@ -1011,7 +1011,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -1029,7 +1029,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -1038,7 +1038,7 @@ public class GoAwayTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
streamFailureLatch.countDown(); streamFailureLatch.countDown();
} }
@ -1083,7 +1083,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -1100,7 +1100,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -1110,7 +1110,7 @@ public class GoAwayTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/"), false), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
clientFailureLatch.countDown(); clientFailureLatch.countDown();
} }
@ -1153,7 +1153,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -1169,7 +1169,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }
@ -1203,7 +1203,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
serverDisconnectLatch.countDown(); serverDisconnectLatch.countDown();
} }
@ -1226,7 +1226,7 @@ public class GoAwayTest extends AbstractClientServerTest
} }
@Override @Override
public void onDisconnect(Session session) public void onDisconnect(Session session, long error, String reason)
{ {
clientDisconnectLatch.countDown(); clientDisconnectLatch.countDown();
} }

View File

@ -186,7 +186,7 @@ public class StreamIdleTimeoutTest extends AbstractClientServerTest
clientSession.newRequest(new HeadersFrame(newRequest("/idle"), false), new Stream.Listener() clientSession.newRequest(new HeadersFrame(newRequest("/idle"), false), new Stream.Listener()
{ {
@Override @Override
public void onFailure(Stream stream, Throwable failure) public void onFailure(Stream stream, long error, Throwable failure)
{ {
// The server idle times out, but did not send any data back. // The server idle times out, but did not send any data back.
// However, the stream is readable and the implementation // However, the stream is readable and the implementation

View File

@ -19,11 +19,14 @@ import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.http3.api.Session; import org.eclipse.jetty.http3.api.Session;
import org.eclipse.jetty.http3.frames.DataFrame; import org.eclipse.jetty.http3.frames.DataFrame;
import org.eclipse.jetty.http3.frames.GoAwayFrame;
import org.eclipse.jetty.http3.internal.HTTP3ErrorCode;
import org.eclipse.jetty.http3.internal.HTTP3Session; import org.eclipse.jetty.http3.internal.HTTP3Session;
import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.Callback;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import static org.awaitility.Awaitility.await; import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
public class UnexpectedFrameTest extends AbstractClientServerTest public class UnexpectedFrameTest extends AbstractClientServerTest
@ -31,30 +34,40 @@ public class UnexpectedFrameTest extends AbstractClientServerTest
@Test @Test
public void testDataBeforeHeaders() throws Exception public void testDataBeforeHeaders() throws Exception
{ {
CountDownLatch serverLatch = new CountDownLatch(1); CountDownLatch serverFailureLatch = new CountDownLatch(1);
start(new Session.Server.Listener() start(new Session.Server.Listener()
{ {
@Override @Override
public void onFailure(Session session, Throwable failure) public void onFailure(Session session, long error, String reason)
{ {
serverLatch.countDown(); assertEquals(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), error);
serverFailureLatch.countDown();
} }
}); });
CountDownLatch clientLatch = new CountDownLatch(1); CountDownLatch clientGoAwayLatch = new CountDownLatch(1);
CountDownLatch clientDisconnectLatch = new CountDownLatch(1);
HTTP3Session clientSession = (HTTP3Session)newSession(new Session.Client.Listener() HTTP3Session clientSession = (HTTP3Session)newSession(new Session.Client.Listener()
{
@Override
public void onGoAway(Session session, GoAwayFrame frame)
{ {
@Override clientGoAwayLatch.countDown();
public void onFailure(Session session, Throwable failure) }
{
clientLatch.countDown(); @Override
} public void onDisconnect(Session session, long error, String reason)
}); {
assertEquals(HTTP3ErrorCode.FRAME_UNEXPECTED_ERROR.code(), error);
clientDisconnectLatch.countDown();
}
});
clientSession.writeMessageFrame(0, new DataFrame(ByteBuffer.allocate(128), false), Callback.NOOP); clientSession.writeMessageFrame(0, new DataFrame(ByteBuffer.allocate(128), false), Callback.NOOP);
assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); assertTrue(serverFailureLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); assertTrue(clientGoAwayLatch.await(5, TimeUnit.SECONDS));
assertTrue(clientDisconnectLatch.await(5, TimeUnit.SECONDS));
await().atMost(1, TimeUnit.SECONDS).until(clientSession::isClosed); await().atMost(1, TimeUnit.SECONDS).until(clientSession::isClosed);
} }

View File

@ -37,6 +37,6 @@ public class CloseInfo
@Override @Override
public String toString() public String toString()
{ {
return String.format("%s@%x[error=0x%s,reason=%s]", getClass().getSimpleName(), hashCode(), Long.toHexString(error()), reason()); return String.format("%s@%x[error=%#x,reason=%s]", getClass().getSimpleName(), hashCode(), error(), reason());
} }
} }