From f7925aebd28341609bb30088f91a8b2075186922 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 21 Aug 2017 12:12:26 +0200 Subject: [PATCH 1/7] Removed debug logging. --- .../java/org/eclipse/jetty/http2/client/StreamResetTest.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java index c39e273294b..8e8554728c1 100644 --- a/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java +++ b/jetty-http2/http2-client/src/test/java/org/eclipse/jetty/http2/client/StreamResetTest.java @@ -66,7 +66,6 @@ import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.FutureCallback; import org.eclipse.jetty.util.FuturePromise; import org.eclipse.jetty.util.IO; -import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.StacklessLogging; import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.hamcrest.Matchers; @@ -428,7 +427,6 @@ public class StreamResetTest extends AbstractTest @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - Log.getLogger(StreamResetTest.class).info("SIMON: uri={}", request.getRequestURI()); phaser.get().countDown(); IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -455,7 +453,6 @@ public class StreamResetTest extends AbstractTest @Override public void onHeaders(Stream stream, HeadersFrame frame) { - Log.getLogger(StreamResetTest.class).info("SIMON: response={}/{}", stream.getId(), frame.getMetaData()); MetaData.Response response = (MetaData.Response)frame.getMetaData(); if (response.getStatus() == HttpStatus.OK_200) latch.get().countDown(); @@ -464,7 +461,6 @@ public class StreamResetTest extends AbstractTest @Override public void onData(Stream stream, DataFrame frame, Callback callback) { - Log.getLogger(StreamResetTest.class).info("SIMON: data={}/{}", stream.getId(), frame); callback.succeeded(); if (frame.isEndStream()) latch.get().countDown(); From 154824049be491b201029f9a1013891f9af514b4 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 21 Aug 2017 15:53:56 +0200 Subject: [PATCH 2/7] Issue #1759 - HTTP/2 producer can block in onReset. Now both failures and timeouts, when they must call the application, do so by dispatching a Runnable to avoid to block the caller thread. --- .../http2/server/HTTP2ServerConnection.java | 10 ++-- .../http2/server/HttpChannelOverHTTP2.java | 46 +++++++++++++------ 2 files changed, 39 insertions(+), 17 deletions(-) diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java index 4a9ebb32bc6..fbf1b9954ed 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java @@ -156,7 +156,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection public boolean onStreamTimeout(IStream stream, Throwable failure) { HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); - boolean result = channel != null && channel.onStreamTimeout(failure); + boolean result = channel != null && channel.onStreamTimeout(failure, task -> offerTask(task, true)); if (LOG.isDebugEnabled()) LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", stream, failure); return result; @@ -168,7 +168,11 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection LOG.debug("Processing failure on {}: {}", stream, failure); HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); if (channel != null) - channel.onFailure(failure); + { + Runnable task = channel.onFailure(failure); + if (task != null) + offerTask(task, true); + } } public boolean onSessionTimeout(Throwable failure) @@ -179,7 +183,7 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection { HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); if (channel != null) - result &= !channel.isRequestExecuting(); + result &= channel.isRequestIdle(); } if (LOG.isDebugEnabled()) LOG.debug("{} idle timeout on {}: {}", result ? "Processed" : "Ignored", session, failure); diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index e55dc27e593..144a8cc8d36 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -20,6 +20,7 @@ package org.eclipse.jetty.http2.server; import java.io.IOException; import java.nio.ByteBuffer; +import java.util.function.Consumer; import org.eclipse.jetty.http.BadMessageException; import org.eclipse.jetty.http.HttpField; @@ -40,6 +41,7 @@ import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpInput; +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.log.Log; @@ -277,32 +279,39 @@ public class HttpChannelOverHTTP2 extends HttpChannel return handle || wasDelayed ? this : null; } - public boolean isRequestExecuting() + public boolean isRequestIdle() { - return !getState().isIdle(); + return getState().isIdle(); } - public boolean onStreamTimeout(Throwable failure) + public boolean onStreamTimeout(Throwable failure, Consumer consumer) { + boolean result = false; + if (isRequestIdle()) + { + consumeInput(); + result = true; + } + getHttpTransport().onStreamTimeout(failure); if (getRequest().getHttpInput().onIdleTimeout(failure)) - handle(); + consumer.accept(this::handleWithContext); - if (isRequestExecuting()) - return false; - - consumeInput(); - return true; + return result; } - public void onFailure(Throwable failure) + public Runnable onFailure(Throwable failure) { getHttpTransport().onStreamFailure(failure); - if (getRequest().getHttpInput().failed(failure)) - handle(); - else - getState().asyncError(failure); + boolean handle = getRequest().getHttpInput().failed(failure); consumeInput(); + return () -> + { + if (handle) + handleWithContext(); + else + getState().asyncError(failure); + }; } protected void consumeInput() @@ -310,6 +319,15 @@ public class HttpChannelOverHTTP2 extends HttpChannel getRequest().getHttpInput().consumeAll(); } + private void handleWithContext() + { + ContextHandler context = getState().getContextHandler(); + if (context != null) + context.handle(getRequest(), this); + else + handle(); + } + /** * If the associated response has the Expect header set to 100 Continue, * then accessing the input stream indicates that the handler/servlet From 6cb6a9e16f32d20c1532236429c8fd72f5688240 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 21 Aug 2017 16:41:04 +0200 Subject: [PATCH 3/7] Fixes #1719 - Improve handling of HTTP/2 queued requests. Clearing _waitingForContent in recycle(). --- .../src/main/java/org/eclipse/jetty/server/HttpInput.java | 1 + 1 file changed, 1 insertion(+) diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java index c348f86caf6..f524dfcfbcf 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpInput.java @@ -91,6 +91,7 @@ public class HttpInput extends ServletInputStream implements Runnable _contentConsumed = 0; _firstByteTimeStamp = -1; _blockUntil = 0; + _waitingForContent = false; } } From b9a946f76eec49a5cce568153638cbb4b480d499 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Mon, 21 Aug 2017 17:00:40 +0200 Subject: [PATCH 4/7] Made test more robust. --- .../org/eclipse/jetty/http/client/ServerTimeoutsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java index eee370dab9d..bfa57beab21 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/ServerTimeoutsTest.java @@ -694,7 +694,7 @@ public class ServerTimeoutsTest extends AbstractTest { try { - Thread.sleep(2 * idleTimeout); + Thread.sleep(idleTimeout + idleTimeout / 2); IO.copy(request.getInputStream(), response.getOutputStream()); } catch (InterruptedException x) @@ -729,7 +729,7 @@ public class ServerTimeoutsTest extends AbstractTest }); // Wait for the server application to block reading. - Thread.sleep(3 * idleTimeout); + Thread.sleep(2 * idleTimeout); content.offer(ByteBuffer.wrap(data2)); content.close(); From 927fe9ec44968f1dd03b5e9d79d23d334ad971ab Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 22 Aug 2017 15:46:33 +0200 Subject: [PATCH 5/7] Callback.NOOP is obviously non-blocking. --- jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java index 64e6263dfcf..92c4f5e80a2 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/Callback.java @@ -30,7 +30,7 @@ public interface Callback * Instance of Adapter that can be used when the callback methods need an empty * implementation without incurring in the cost of allocating a new Adapter object. */ - Callback NOOP = new Callback() + Callback NOOP = new Callback.NonBlocking() { }; From 17116423093594d85e426afe97afabc14ac2085a Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 22 Aug 2017 15:47:17 +0200 Subject: [PATCH 6/7] Added guard to avoid the case where count == 0. --- .../src/main/java/org/eclipse/jetty/util/CountingCallback.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java b/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java index f6538d7621b..4530deeb23d 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/CountingCallback.java @@ -45,6 +45,8 @@ public class CountingCallback extends Callback.Nested public CountingCallback(Callback callback, int count) { super(callback); + if (count < 1) + throw new IllegalArgumentException(); this.count = new AtomicInteger(count); } From f9ff9e1226ce873d154ce44273dd0387373eaee6 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 22 Aug 2017 15:54:40 +0200 Subject: [PATCH 7/7] Issue #1759 - HTTP/2 producer can block in onReset. Because now the failures are asynchronous, code that was executed after invoking the failure listener must be now executed after the asynchronous processing done by the listener and therefore Callbacks are introduced. --- .../eclipse/jetty/http2/HTTP2Connection.java | 4 +- .../org/eclipse/jetty/http2/HTTP2Session.java | 94 +++++++++++++++++-- .../org/eclipse/jetty/http2/api/Session.java | 36 ++++++- .../http2/server/HTTP2ServerConnection.java | 25 ++++- .../server/HTTP2ServerConnectionFactory.java | 10 +- .../http2/server/HttpChannelOverHTTP2.java | 41 ++++++-- 6 files changed, 177 insertions(+), 33 deletions(-) diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java index 7cabc376a82..676e0ad0737 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Connection.java @@ -175,6 +175,7 @@ public class HTTP2Connection extends AbstractConnection { private final Callback fillCallback = new FillCallback(); private ByteBuffer buffer; + private boolean shutdown; @Override public Runnable produce() @@ -185,7 +186,7 @@ public class HTTP2Connection extends AbstractConnection if (task != null) return task; - if (isFillInterested()) + if (isFillInterested() || shutdown) return null; if (buffer == null) @@ -221,6 +222,7 @@ public class HTTP2Connection extends AbstractConnection else if (filled < 0) { release(); + shutdown = true; session.onShutdown(); return null; } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java index 1d1772c190a..383b2e12c57 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/HTTP2Session.java @@ -421,8 +421,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio { // We received a GO_AWAY, so try to write // what's in the queue and then disconnect. - notifyClose(this, frame); - control(null, Callback.NOOP, new DisconnectFrame()); + notifyClose(this, frame, new DisconnectCallback()); return; } break; @@ -462,8 +461,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio @Override public void onConnectionFailure(int error, String reason) { - notifyFailure(this, new IOException(String.format("%d/%s", error, reason))); - close(error, reason, Callback.NOOP); + notifyFailure(this, new IOException(String.format("%d/%s", error, reason)), new CloseCallback(error, reason)); } @Override @@ -991,8 +989,7 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio protected void abort(Throwable failure) { - notifyFailure(this, failure); - terminate(failure); + notifyFailure(this, failure, new TerminateCallback(failure)); } public boolean isDisconnected() @@ -1054,11 +1051,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected void notifyClose(Session session, GoAwayFrame frame) + protected void notifyClose(Session session, GoAwayFrame frame, Callback callback) { try { - listener.onClose(session, frame); + listener.onClose(session, frame, callback); } catch (Throwable x) { @@ -1079,11 +1076,11 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio } } - protected void notifyFailure(Session session, Throwable failure) + protected void notifyFailure(Session session, Throwable failure, Callback callback) { try { - listener.onFailure(session, failure); + listener.onFailure(session, failure, callback); } catch (Throwable x) { @@ -1322,4 +1319,81 @@ public abstract class HTTP2Session extends ContainerLifeCycle implements ISessio promise.failed(x); } } + + private class CloseCallback implements Callback.NonBlocking + { + private final int error; + private final String reason; + + private CloseCallback(int error, String reason) + { + this.error = error; + this.reason = reason; + } + + @Override + public void succeeded() + { + complete(); + } + + @Override + public void failed(Throwable x) + { + complete(); + } + + private void complete() + { + close(error, reason, Callback.NOOP); + } + } + + private class DisconnectCallback implements Callback.NonBlocking + { + @Override + public void succeeded() + { + complete(); + } + + @Override + public void failed(Throwable x) + { + complete(); + } + + private void complete() + { + control(null, Callback.NOOP, new DisconnectFrame()); + } + } + + private class TerminateCallback implements Callback.NonBlocking + { + private final Throwable failure; + + private TerminateCallback(Throwable failure) + { + this.failure = failure; + } + + @Override + public void succeeded() + { + complete(); + } + + @Override + public void failed(Throwable x) + { + failure.addSuppressed(x); + complete(); + } + + private void complete() + { + terminate(failure); + } + } } diff --git a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java index e2c4dbb9ba9..10642d7351d 100644 --- a/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java +++ b/jetty-http2/http2-common/src/main/java/org/eclipse/jetty/http2/api/Session.java @@ -195,9 +195,23 @@ public interface Session /** *

Callback method invoked when a GOAWAY frame has been received.

* - * @param session the session - * @param frame the GOAWAY frame received + * @param session the session + * @param frame the GOAWAY frame received + * @param callback the callback to notify of the GOAWAY processing */ + public default void onClose(Session session, GoAwayFrame frame, Callback callback) + { + try + { + onClose(session, frame); + callback.succeeded(); + } + catch (Throwable x) + { + callback.failed(x); + } + } + public void onClose(Session session, GoAwayFrame frame); /** @@ -210,9 +224,23 @@ public interface Session /** *

Callback method invoked when a failure has been detected for this session.

* - * @param session the session - * @param failure the failure + * @param session the session + * @param failure the failure + * @param callback the callback to notify of failure processing */ + public default void onFailure(Session session, Throwable failure, Callback callback) + { + try + { + onFailure(session, failure); + callback.succeeded(); + } + catch (Throwable x) + { + callback.failed(x); + } + } + public void onFailure(Session session, Throwable failure); /** diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java index fbf1b9954ed..b981c5fb47a 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnection.java @@ -22,6 +22,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Queue; import java.util.concurrent.Executor; @@ -55,6 +56,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.util.B64Code; import org.eclipse.jetty.util.BufferUtil; import org.eclipse.jetty.util.Callback; +import org.eclipse.jetty.util.CountingCallback; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.thread.ExecutionStrategy; @@ -162,17 +164,21 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection return result; } - public void onStreamFailure(IStream stream, Throwable failure) + public void onStreamFailure(IStream stream, Throwable failure, Callback callback) { if (LOG.isDebugEnabled()) LOG.debug("Processing failure on {}: {}", stream, failure); HttpChannelOverHTTP2 channel = (HttpChannelOverHTTP2)stream.getAttribute(IStream.CHANNEL_ATTRIBUTE); if (channel != null) { - Runnable task = channel.onFailure(failure); + Runnable task = channel.onFailure(failure, callback); if (task != null) offerTask(task, true); } + else + { + callback.succeeded(); + } } public boolean onSessionTimeout(Throwable failure) @@ -190,13 +196,22 @@ public class HTTP2ServerConnection extends HTTP2Connection implements Connection return result; } - public void onSessionFailure(Throwable failure) + public void onSessionFailure(Throwable failure, Callback callback) { ISession session = getSession(); if (LOG.isDebugEnabled()) LOG.debug("Processing failure on {}: {}", session, failure); - for (Stream stream : session.getStreams()) - onStreamFailure((IStream)stream, failure); + Collection streams = session.getStreams(); + if (streams.isEmpty()) + { + callback.succeeded(); + } + else + { + CountingCallback counter = new CountingCallback(callback, streams.size()); + for (Stream stream : streams) + onStreamFailure((IStream)stream, failure, counter); + } } public void push(Connector connector, IStream stream, MetaData.Request request) diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java index f16b9a70be4..491b6c4ea7c 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HTTP2ServerConnectionFactory.java @@ -123,7 +123,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF } @Override - public void onClose(Session session, GoAwayFrame frame) + public void onClose(Session session, GoAwayFrame frame, Callback callback) { ErrorCode error = ErrorCode.from(frame.getError()); if (error == null) @@ -131,13 +131,13 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF String reason = frame.tryConvertPayload(); if (reason != null && !reason.isEmpty()) reason = " (" + reason + ")"; - getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason)); + getConnection().onSessionFailure(new EofException("HTTP/2 " + error + reason), callback); } @Override - public void onFailure(Session session, Throwable failure) + public void onFailure(Session session, Throwable failure, Callback callback) { - getConnection().onSessionFailure(failure); + getConnection().onSessionFailure(failure, callback); } @Override @@ -167,7 +167,7 @@ public class HTTP2ServerConnectionFactory extends AbstractHTTP2ServerConnectionF ErrorCode error = ErrorCode.from(frame.getError()); if (error == null) error = ErrorCode.CANCEL_STREAM_ERROR; - getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error)); + getConnection().onStreamFailure((IStream)stream, new EofException("HTTP/2 " + error), Callback.NOOP); } @Override diff --git a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java index 144a8cc8d36..79cdcc98290 100644 --- a/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java +++ b/jetty-http2/http2-server/src/main/java/org/eclipse/jetty/http2/server/HttpChannelOverHTTP2.java @@ -300,18 +300,12 @@ public class HttpChannelOverHTTP2 extends HttpChannel return result; } - public Runnable onFailure(Throwable failure) + public Runnable onFailure(Throwable failure, Callback callback) { getHttpTransport().onStreamFailure(failure); boolean handle = getRequest().getHttpInput().failed(failure); consumeInput(); - return () -> - { - if (handle) - handleWithContext(); - else - getState().asyncError(failure); - }; + return new FailureTask(failure, callback, handle); } protected void consumeInput() @@ -366,4 +360,35 @@ public class HttpChannelOverHTTP2 extends HttpChannel streamId = stream.getId(); return String.format("%s#%d", super.toString(), getStream() == null ? -1 : streamId); } + + private class FailureTask implements Runnable + { + private final Throwable failure; + private final Callback callback; + private final boolean handle; + + public FailureTask(Throwable failure, Callback callback, boolean handle) + { + this.failure = failure; + this.callback = callback; + this.handle = handle; + } + + @Override + public void run() + { + try + { + if (handle) + handleWithContext(); + else + getState().asyncError(failure); + callback.succeeded(); + } + catch (Throwable x) + { + callback.failed(x); + } + } + } }