From 646707b85ca265cf70a9642fa89b57b4eef849b5 Mon Sep 17 00:00:00 2001 From: Lachlan Roberts Date: Thu, 12 Aug 2021 15:50:25 +1000 Subject: [PATCH 01/24] Issue #6602 - do not invoke SessionListener onOpen if session has been closed in OnOpen Signed-off-by: Lachlan Roberts --- .../jetty/websocket/core/CoreSession.java | 2 +- .../common/JavaxWebSocketFrameHandler.java | 7 +- .../javax/tests/CloseInOnOpenTest.java | 97 +++++++++++++++++++ .../common/JettyWebSocketFrameHandler.java | 5 +- .../websocket/tests/CloseInOnOpenTest.java | 95 ++++++++++++++++++ 5 files changed, 203 insertions(+), 3 deletions(-) create mode 100644 jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java create mode 100644 jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java diff --git a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java index 7c6bd9bb4ec0..bbaf05c4e5a7 100644 --- a/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java +++ b/jetty-websocket/websocket-core-common/src/main/java/org/eclipse/jetty/websocket/core/CoreSession.java @@ -256,7 +256,7 @@ public SocketAddress getRemoteAddress() @Override public boolean isOutputOpen() { - return false; + return true; } @Override diff --git a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java index 99cfd8bb2e6f..030312178243 100644 --- a/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java +++ b/jetty-websocket/websocket-javax-common/src/main/java/org/eclipse/jetty/websocket/javax/common/JavaxWebSocketFrameHandler.java @@ -133,6 +133,9 @@ public void onOpen(CoreSession coreSession, Callback callback) // Rewire EndpointConfig to call CoreSession setters if Jetty specific properties are set. endpointConfig = getWrappedEndpointConfig(); session = new JavaxWebSocketSession(container, coreSession, this, endpointConfig); + if (!session.isOpen()) + throw new IllegalStateException("Session is not open"); + openHandle = InvokerUtils.bindTo(openHandle, session, endpointConfig); closeHandle = InvokerUtils.bindTo(closeHandle, session); errorHandle = InvokerUtils.bindTo(errorHandle, session); @@ -171,7 +174,9 @@ public void onOpen(CoreSession coreSession, Callback callback) if (openHandle != null) openHandle.invoke(); - container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session)); + if (session.isOpen()) + container.notifySessionListeners((listener) -> listener.onJavaxWebSocketSessionOpened(session)); + callback.succeeded(); } catch (Throwable cause) diff --git a/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java new file mode 100644 index 000000000000..859a8568e5e6 --- /dev/null +++ b/jetty-websocket/websocket-javax-tests/src/test/java/org/eclipse/jetty/websocket/javax/tests/CloseInOnOpenTest.java @@ -0,0 +1,97 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.javax.tests; + +import java.net.URI; +import java.util.concurrent.TimeUnit; +import javax.websocket.CloseReason; +import javax.websocket.OnOpen; +import javax.websocket.Session; +import javax.websocket.server.ServerEndpoint; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.javax.client.internal.JavaxWebSocketClientContainer; +import org.eclipse.jetty.websocket.javax.server.config.JavaxWebSocketServletContainerInitializer; +import org.eclipse.jetty.websocket.javax.server.internal.JavaxWebSocketServerContainer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CloseInOnOpenTest +{ + private Server server; + private ServerConnector connector; + private JavaxWebSocketServerContainer serverContainer; + private JavaxWebSocketClientContainer client; + + @BeforeEach + public void beforeEach() throws Exception + { + server = new Server(); + + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + JavaxWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> + wsContainer.addEndpoint(ClosingListener.class)); + server.start(); + + serverContainer = JavaxWebSocketServerContainer.getContainer(context.getServletContext()); + assertNotNull(serverContainer); + + client = new JavaxWebSocketClientContainer(); + client.start(); + } + + @AfterEach + public void afterEach() throws Exception + { + client.stop(); + server.stop(); + } + + @Test + public void testCloseInOnWebSocketConnect() throws Exception + { + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws"); + EventSocket clientEndpoint = new EventSocket(); + + client.connectToServer(clientEndpoint, uri); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeReason.getCloseCode(), is(CloseReason.CloseCodes.VIOLATED_POLICY)); + + assertThat(serverContainer.getOpenSessions().size(), is(0)); + } + + @ServerEndpoint("/ws") + public static class ClosingListener + { + @OnOpen + public void onWebSocketConnect(Session session) throws Exception + { + session.close(new CloseReason(CloseReason.CloseCodes.VIOLATED_POLICY, "I am a WS that closes immediately")); + } + } +} diff --git a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java index d3235ad0ed73..992c9042dd2a 100644 --- a/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java +++ b/jetty-websocket/websocket-jetty-common/src/main/java/org/eclipse/jetty/websocket/common/JettyWebSocketFrameHandler.java @@ -151,6 +151,8 @@ public void onOpen(CoreSession coreSession, Callback callback) { customizer.customize(coreSession); session = new WebSocketSession(container, coreSession, this); + if (!session.isOpen()) + throw new IllegalStateException("Session is not open"); frameHandle = InvokerUtils.bindTo(frameHandle, session); openHandle = InvokerUtils.bindTo(openHandle, session); @@ -172,7 +174,8 @@ public void onOpen(CoreSession coreSession, Callback callback) if (openHandle != null) openHandle.invoke(); - container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session)); + if (session.isOpen()) + container.notifySessionListeners((listener) -> listener.onWebSocketSessionOpened(session)); callback.succeeded(); demand(); diff --git a/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java new file mode 100644 index 000000000000..903c51c5ed6d --- /dev/null +++ b/jetty-websocket/websocket-jetty-tests/src/test/java/org/eclipse/jetty/websocket/tests/CloseInOnOpenTest.java @@ -0,0 +1,95 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.websocket.tests; + +import java.net.URI; +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.websocket.api.Session; +import org.eclipse.jetty.websocket.api.StatusCode; +import org.eclipse.jetty.websocket.api.WebSocketConnectionListener; +import org.eclipse.jetty.websocket.client.WebSocketClient; +import org.eclipse.jetty.websocket.server.JettyWebSocketServerContainer; +import org.eclipse.jetty.websocket.server.config.JettyWebSocketServletContainerInitializer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CloseInOnOpenTest +{ + private Server server; + private ServerConnector connector; + private JettyWebSocketServerContainer serverContainer; + private WebSocketClient client; + + @BeforeEach + public void beforeEach() throws Exception + { + server = new Server(); + + connector = new ServerConnector(server); + server.addConnector(connector); + + ServletContextHandler context = new ServletContextHandler(); + context.setContextPath("/"); + server.setHandler(context); + + JettyWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> + wsContainer.addMapping("/ws", (req, resp) -> new ClosingListener())); + server.start(); + + serverContainer = JettyWebSocketServerContainer.getContainer(context.getServletContext()); + assertNotNull(serverContainer); + + client = new WebSocketClient(); + client.start(); + } + + @AfterEach + public void afterEach() throws Exception + { + client.stop(); + server.stop(); + } + + @Test + public void testCloseInOnWebSocketConnect() throws Exception + { + URI uri = URI.create("ws://localhost:" + connector.getLocalPort() + "/ws"); + EventSocket clientEndpoint = new EventSocket(); + + client.connect(clientEndpoint, uri).get(5, TimeUnit.SECONDS); + assertTrue(clientEndpoint.closeLatch.await(5, TimeUnit.SECONDS)); + assertThat(clientEndpoint.closeCode, is(StatusCode.POLICY_VIOLATION)); + + assertThat(serverContainer.getOpenSessions().size(), is(0)); + } + + public static class ClosingListener implements WebSocketConnectionListener + { + @Override + public void onWebSocketConnect(Session session) + { + session.close(StatusCode.POLICY_VIOLATION, "I am a WS that closes immediately"); + } + } +} From 26d144d708fbec4122869df2482048fdd521406b Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Fri, 30 Jul 2021 11:49:06 +0200 Subject: [PATCH 02/24] #6327 enable AsyncIOServletTest.testAsyncWriteClosed Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/server/HttpOutput.java | 6 +++ .../jetty/http/client/AsyncIOServletTest.java | 53 +++++++++++++++++-- .../jetty/server/HttpOutputHelper.java | 22 ++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) create mode 100644 tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index f8df5ea27a26..c061c04f7cc3 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -1516,6 +1516,12 @@ public void run() } } + // For testing + ApiState getApiState() + { + return _apiState; + } + private String stateString() { return String.format("s=%s,api=%s,sc=%b,e=%s", _state, _apiState, _softClose, _onError); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index d1c573059be6..60f3ffca1877 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -20,8 +20,11 @@ import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.Deque; +import java.util.Objects; import java.util.Queue; +import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -65,6 +68,8 @@ import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpInput.Content; +import org.eclipse.jetty.server.HttpOutput; +import org.eclipse.jetty.server.HttpOutputHelper; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; @@ -74,8 +79,6 @@ import org.eclipse.jetty.util.compression.InflaterPool; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -398,8 +401,6 @@ public void onError(Throwable t) @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @Tag("Unstable") - @Disabled public void testAsyncWriteClosed(Transport transport) throws Exception { init(transport); @@ -431,7 +432,18 @@ public void onWritePossible() throws IOException // Wait for the failure to arrive to // the server while we are about to write. - sleep(2000); + try + { + Await.await().atMost(5, TimeUnit.SECONDS).until(() -> + { + out.write(new byte[0]); + return !HttpOutputHelper.getApiState(((HttpOutput)out)).equals("READY"); + }); + } + catch (Exception e) + { + throw new AssertionError(e); + } out.write(data); } @@ -1854,4 +1866,35 @@ public void stopServer() throws Exception super.stopServer(); } } + + static class Await + { + private Duration duration; + + public static Await await() + { + return new Await(); + } + + public Await atMost(long time, TimeUnit unit) + { + duration = Duration.ofMillis(unit.toMillis(time)); + return this; + } + + public void until(Callable condition) throws Exception + { + Objects.requireNonNull(duration); + long start = System.nanoTime(); + + while (true) + { + if (condition.call()) + return; + if (duration.minus(Duration.ofNanos(System.nanoTime() - start)).isNegative()) + throw new AssertionError("Duration expired"); + Thread.sleep(10); + } + } + } } diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java new file mode 100644 index 000000000000..d5bc9312bd65 --- /dev/null +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java @@ -0,0 +1,22 @@ +// +// ======================================================================== +// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. +// +// This program and the accompanying materials are made available under the +// terms of the Eclipse Public License v. 2.0 which is available at +// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 +// which is available at https://www.apache.org/licenses/LICENSE-2.0. +// +// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 +// ======================================================================== +// + +package org.eclipse.jetty.server; + +public class HttpOutputHelper +{ + public static String getApiState(HttpOutput httpOutput) + { + return httpOutput.getApiState().toString(); + } +} From 5dc856196e6e16488fafb82dc854bba09c3c9a10 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 13:04:44 +0200 Subject: [PATCH 03/24] #6327 extract HttpOutput._apiState value from toString Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/server/HttpOutput.java | 6 ----- .../jetty/http/client/AsyncIOServletTest.java | 5 ++--- .../jetty/server/HttpOutputHelper.java | 22 ------------------- 3 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java index c061c04f7cc3..f8df5ea27a26 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/HttpOutput.java @@ -1516,12 +1516,6 @@ public void run() } } - // For testing - ApiState getApiState() - { - return _apiState; - } - private String stateString() { return String.format("s=%s,api=%s,sc=%b,e=%s", _state, _apiState, _softClose, _onError); diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index 60f3ffca1877..358e6afa7c05 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -68,8 +68,6 @@ import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpInput.Content; -import org.eclipse.jetty.server.HttpOutput; -import org.eclipse.jetty.server.HttpOutputHelper; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; @@ -437,7 +435,8 @@ public void onWritePossible() throws IOException Await.await().atMost(5, TimeUnit.SECONDS).until(() -> { out.write(new byte[0]); - return !HttpOutputHelper.getApiState(((HttpOutput)out)).equals("READY"); + // Extract HttpOutput._apiState value from toString. + return !out.toString().split(",")[1].split("=")[1].equals("READY"); }); } catch (Exception e) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java deleted file mode 100644 index d5bc9312bd65..000000000000 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/server/HttpOutputHelper.java +++ /dev/null @@ -1,22 +0,0 @@ -// -// ======================================================================== -// Copyright (c) 1995-2021 Mort Bay Consulting Pty Ltd and others. -// -// This program and the accompanying materials are made available under the -// terms of the Eclipse Public License v. 2.0 which is available at -// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 -// which is available at https://www.apache.org/licenses/LICENSE-2.0. -// -// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 -// ======================================================================== -// - -package org.eclipse.jetty.server; - -public class HttpOutputHelper -{ - public static String getApiState(HttpOutput httpOutput) - { - return httpOutput.getApiState().toString(); - } -} From 7867d51c919352a247875ec0fa93fb000412d27f Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 13:05:44 +0200 Subject: [PATCH 04/24] #6327 replace Await helper with awaitility Signed-off-by: Ludovic Orban --- jetty-util/pom.xml | 5 +++ .../jetty/util/BlockingArrayQueueTest.java | 36 +----------------- pom.xml | 5 +++ tests/test-http-client-transport/pom.xml | 5 +++ .../jetty/http/client/AsyncIOServletTest.java | 37 +------------------ 5 files changed, 18 insertions(+), 70 deletions(-) diff --git a/jetty-util/pom.xml b/jetty-util/pom.xml index bf58de61ba84..98b188a9e786 100644 --- a/jetty-util/pom.xml +++ b/jetty-util/pom.xml @@ -63,6 +63,11 @@ org.slf4j slf4j-api + + org.awaitility + awaitility + test + org.eclipse.jetty.toolchain jetty-perf-helper diff --git a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java index eac290032191..ba6da7943ad4 100644 --- a/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java +++ b/jetty-util/src/test/java/org/eclipse/jetty/util/BlockingArrayQueueTest.java @@ -13,13 +13,10 @@ package org.eclipse.jetty.util; -import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; -import java.util.Objects; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.ThreadLocalRandom; @@ -30,7 +27,7 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.Test; -import static org.eclipse.jetty.util.BlockingArrayQueueTest.Await.await; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -528,35 +525,4 @@ public void testDrainTo() throws Exception assertThat(queue.size(), Matchers.is(0)); assertThat(queue, Matchers.empty()); } - - static class Await - { - private Duration duration; - - public static Await await() - { - return new Await(); - } - - public Await atMost(long time, TimeUnit unit) - { - duration = Duration.ofMillis(unit.toMillis(time)); - return this; - } - - public void until(Callable condition) throws Exception - { - Objects.requireNonNull(duration); - long start = System.nanoTime(); - - while (true) - { - if (condition.call()) - return; - if (duration.minus(Duration.ofNanos(System.nanoTime() - start)).isNegative()) - throw new AssertionError("Duration expired"); - Thread.sleep(10); - } - } - } } diff --git a/pom.xml b/pom.xml index 3048dc8de693..2cfbc481c778 100644 --- a/pom.xml +++ b/pom.xml @@ -1131,6 +1131,11 @@ hamcrest ${hamcrest.version} + + org.awaitility + awaitility + 4.1.0 + org.testcontainers testcontainers-bom diff --git a/tests/test-http-client-transport/pom.xml b/tests/test-http-client-transport/pom.xml index 9c2aab3097d6..8c508d35092b 100644 --- a/tests/test-http-client-transport/pom.xml +++ b/tests/test-http-client-transport/pom.xml @@ -46,6 +46,11 @@ slf4j-api test + + org.awaitility + awaitility + test + org.eclipse.jetty jetty-alpn-java-client diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index 358e6afa7c05..9e11dba26c0a 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -20,11 +20,8 @@ import java.io.UncheckedIOException; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; -import java.time.Duration; import java.util.Deque; -import java.util.Objects; import java.util.Queue; -import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CountDownLatch; @@ -81,6 +78,7 @@ import org.junit.jupiter.params.provider.ArgumentsSource; import static java.nio.ByteBuffer.wrap; +import static org.awaitility.Awaitility.await; import static org.eclipse.jetty.http.client.Transport.FCGI; import static org.eclipse.jetty.http.client.Transport.H2C; import static org.eclipse.jetty.http.client.Transport.HTTP; @@ -432,7 +430,7 @@ public void onWritePossible() throws IOException // the server while we are about to write. try { - Await.await().atMost(5, TimeUnit.SECONDS).until(() -> + await().atMost(5, TimeUnit.SECONDS).until(() -> { out.write(new byte[0]); // Extract HttpOutput._apiState value from toString. @@ -1865,35 +1863,4 @@ public void stopServer() throws Exception super.stopServer(); } } - - static class Await - { - private Duration duration; - - public static Await await() - { - return new Await(); - } - - public Await atMost(long time, TimeUnit unit) - { - duration = Duration.ofMillis(unit.toMillis(time)); - return this; - } - - public void until(Callable condition) throws Exception - { - Objects.requireNonNull(duration); - long start = System.nanoTime(); - - while (true) - { - if (condition.call()) - return; - if (duration.minus(Duration.ofNanos(System.nanoTime() - start)).isNegative()) - throw new AssertionError("Duration expired"); - Thread.sleep(10); - } - } - } } From 2868eea9ccdb1cf081ac3bd1812aa3bccb1d3a3b Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 13:25:27 +0200 Subject: [PATCH 05/24] #6327 enable all disabled tests in HttpClientContinueTest Signed-off-by: Ludovic Orban --- .../http/client/HttpClientContinueTest.java | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java index 1eb3d3e2be78..7bfdc6e2531a 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java @@ -25,6 +25,7 @@ import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; @@ -45,11 +46,10 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.util.IO; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; +import static org.awaitility.Awaitility.await; import static org.eclipse.jetty.http.client.Transport.FCGI; import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -319,12 +319,10 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception { init(transport); - long idleTimeout = 1000; + long idleTimeout = 100; scenario.startServer(new AbstractHandler() { @Override @@ -366,12 +364,10 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception { init(transport); - long idleTimeout = 1000; + long idleTimeout = 100; scenario.startServer(new AbstractHandler() { @Override @@ -474,10 +470,10 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport transport) throws Exception { + AtomicReference handlerThread = new AtomicReference<>(); + CountDownLatch demandLatch = new CountDownLatch(3); init(transport); scenario.start(new AbstractHandler() { @@ -485,6 +481,7 @@ public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { baseRequest.setHandled(true); + handlerThread.set(Thread.currentThread()); // Send 100-Continue and echo the content IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -496,8 +493,16 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques System.arraycopy(chunk1, 0, data, 0, chunk1.length); System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); - CountDownLatch latch = new CountDownLatch(1); - AsyncRequestContent content = new AsyncRequestContent(); + CountDownLatch requestLatch = new CountDownLatch(1); + AsyncRequestContent content = new AsyncRequestContent() + { + @Override + public void demand() + { + super.demand(); + demandLatch.countDown(); + } + }; scenario.client.newRequest(scenario.newURI()) .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) .body(content) @@ -507,28 +512,32 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques public void onComplete(Result result) { assertArrayEquals(data, getContent()); - latch.countDown(); + requestLatch.countDown(); } }); - Thread.sleep(1000); + // Wait for the handler thread to be blocked in IO. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + Thread thread = handlerThread.get(); + return thread != null && thread.getState() == Thread.State.WAITING; + }); content.offer(ByteBuffer.wrap(chunk1)); - Thread.sleep(1000); + assertTrue(demandLatch.await(5, TimeUnit.SECONDS)); content.offer(ByteBuffer.wrap(chunk2)); content.close(); - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(requestLatch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testExpect100ContinueWithInitialAndDeferredContentRespond100Continue(Transport transport) throws Exception { + AtomicReference handlerThread = new AtomicReference<>(); init(transport); scenario.start(new AbstractHandler() { @@ -536,6 +545,7 @@ public void testExpect100ContinueWithInitialAndDeferredContentRespond100Continue public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException { baseRequest.setHandled(true); + handlerThread.set(Thread.currentThread()); // Send 100-Continue and echo the content IO.copy(request.getInputStream(), response.getOutputStream()); } @@ -562,7 +572,12 @@ public void onComplete(Result result) } }); - Thread.sleep(1000); + // Wait for the handler thread to be blocked in IO. + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + Thread thread = handlerThread.get(); + return thread != null && thread.getState() == Thread.State.WAITING; + }); content.offer(ByteBuffer.wrap(chunk2)); content.close(); From 37eb7909be7a7dfaa4c55b7122bb008d766b9181 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 13:28:10 +0200 Subject: [PATCH 06/24] #6327 enable HttpClientStreamTest.testUploadWithDeferredContentProviderFromInputStream Signed-off-by: Ludovic Orban --- .../http/client/HttpClientStreamTest.java | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java index 70639327cebc..a4f3b8008955 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java @@ -58,7 +58,6 @@ import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.IO; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -666,7 +665,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, @ParameterizedTest @ArgumentsSource(TransportProvider.class) - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testUploadWithDeferredContentProviderFromInputStream(Transport transport) throws Exception { init(transport); @@ -680,8 +678,17 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, } }); - CountDownLatch latch = new CountDownLatch(1); - try (AsyncRequestContent content = new AsyncRequestContent()) + CountDownLatch demandLatch = new CountDownLatch(2); + CountDownLatch requestLatch = new CountDownLatch(1); + try (AsyncRequestContent content = new AsyncRequestContent() + { + @Override + public void demand() + { + demandLatch.countDown(); + super.demand(); + } + }) { scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) @@ -689,11 +696,11 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == 200) - latch.countDown(); + requestLatch.countDown(); }); // Make sure we provide the content *after* the request has been "sent". - Thread.sleep(1000); + assertTrue(demandLatch.await(5, TimeUnit.SECONDS)); try (ByteArrayInputStream input = new ByteArrayInputStream(new byte[1024])) { @@ -705,7 +712,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, } } } - assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertTrue(requestLatch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest From 999b3ca11e0ad5d07ec569de70737e3bd2fdf2c7 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 13:28:53 +0200 Subject: [PATCH 07/24] #6327 enable ResourceHandlerTest.testSlowBiggest Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/server/handler/ResourceHandlerTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java index ad2aef8768cc..9b79aba3cd10 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ResourceHandlerTest.java @@ -40,7 +40,6 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.eclipse.jetty.http.HttpHeader.CONTENT_LENGTH; import static org.eclipse.jetty.http.HttpHeader.CONTENT_TYPE; @@ -277,7 +276,6 @@ public void testWelcomeRedirect() throws Exception } @Test - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testSlowBiggest() throws Exception { _connector.setIdleTimeout(9000); @@ -307,7 +305,7 @@ public void testSlowBiggest() throws Exception ByteBuffer buffer = null; while (true) { - Thread.sleep(25); + Thread.sleep(10); int len = in.read(array); if (len < 0) break; From 56eed8211e57fc2a5d7bddebecb15a2057b37fd9 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 2 Aug 2021 16:38:33 +0200 Subject: [PATCH 08/24] #6327 enable HttpClientTest.testRequestIdleTimeout Signed-off-by: Ludovic Orban --- .../test/java/org/eclipse/jetty/client/HttpClientTest.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 2a33f7148839..7604ae1a28cb 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -91,7 +91,6 @@ import org.eclipse.jetty.util.SocketAddressResolver; import org.hamcrest.Matchers; import org.junit.jupiter.api.Assumptions; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; @@ -690,10 +689,9 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testRequestIdleTimeout(Scenario scenario) throws Exception { - long idleTimeout = 1000; + long idleTimeout = 100; start(scenario, new AbstractHandler() { @Override From 8766bddb5072c086f505147c1ee62c744b90b00f Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 4 Aug 2021 10:16:57 +0200 Subject: [PATCH 09/24] #6327 improve test Signed-off-by: Ludovic Orban --- .../jetty/http/client/AsyncIOServletTest.java | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java index 9e11dba26c0a..571839413d85 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/AsyncIOServletTest.java @@ -60,11 +60,13 @@ import org.eclipse.jetty.http2.api.Session; import org.eclipse.jetty.http2.client.http.HttpConnectionOverHTTP2; import org.eclipse.jetty.io.Connection; +import org.eclipse.jetty.io.EofException; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.HttpChannel; import org.eclipse.jetty.server.HttpInput; import org.eclipse.jetty.server.HttpInput.Content; +import org.eclipse.jetty.server.HttpOutput; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.ContextHandler.Context; @@ -401,12 +403,7 @@ public void testAsyncWriteClosed(Transport transport) throws Exception { init(transport); - String text = "Now is the winter of our discontent. How Now Brown Cow. The quick brown fox jumped over the lazy dog.\n"; - for (int i = 0; i < 10; i++) - { - text = text + text; - } - byte[] data = text.getBytes(StandardCharsets.UTF_8); + byte[] data = new byte[1024]; CountDownLatch errorLatch = new CountDownLatch(1); scenario.start(new HttpServlet() @@ -432,17 +429,22 @@ public void onWritePossible() throws IOException { await().atMost(5, TimeUnit.SECONDS).until(() -> { - out.write(new byte[0]); - // Extract HttpOutput._apiState value from toString. - return !out.toString().split(",")[1].split("=")[1].equals("READY"); + try + { + if (out.isReady()) + ((HttpOutput)out).write(ByteBuffer.wrap(data)); + return false; + } + catch (EofException e) + { + return true; + } }); } catch (Exception e) { throw new AssertionError(e); } - - out.write(data); } @Override From 72505af84641d07515c9d40a474e6da922f0c887 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 4 Aug 2021 10:42:48 +0200 Subject: [PATCH 10/24] #6327 stop relying on timeouts to abort tested requests Signed-off-by: Ludovic Orban --- .../http/client/HttpClientContinueTest.java | 95 ++++++++++++------- 1 file changed, 59 insertions(+), 36 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java index 7bfdc6e2531a..55cd8ae642a8 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientContinueTest.java @@ -14,6 +14,7 @@ package org.eclipse.jetty.http.client; import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -28,6 +29,7 @@ import java.util.concurrent.atomic.AtomicReference; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; +import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -322,31 +324,37 @@ public void onComplete(Result result) public void testExpect100ContinueWithContentWithResponseFailureBefore100Continue(Transport transport) throws Exception { init(transport); - long idleTimeout = 100; + AtomicReference clientRequestRef = new AtomicReference<>(); + CountDownLatch clientLatch = new CountDownLatch(1); + CountDownLatch serverLatch = new CountDownLatch(1); + scenario.startServer(new AbstractHandler() { @Override public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException { baseRequest.setHandled(true); + clientRequestRef.get().abort(new Exception("abort!")); try { - TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + if (!clientLatch.await(5, TimeUnit.SECONDS)) + throw new ServletException("Server timed out on client latch"); + serverLatch.countDown(); } - catch (InterruptedException x) + catch (InterruptedException e) { - throw new ServletException(x); + throw new ServletException(e); } } }); - scenario.startClient(httpClient -> httpClient.setIdleTimeout(2 * idleTimeout)); + scenario.startClient(); byte[] content = new byte[1024]; - CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + org.eclipse.jetty.client.api.Request clientRequest = scenario.client.newRequest(scenario.newURI()); + clientRequestRef.set(clientRequest); + clientRequest .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) .body(new BytesRequestContent(content)) - .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) .send(new BufferingResponseListener() { @Override @@ -355,11 +363,12 @@ public void onComplete(Result result) assertTrue(result.isFailed()); assertNotNull(result.getRequestFailure()); assertNotNull(result.getResponseFailure()); - latch.countDown(); + clientLatch.countDown(); } }); - assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS)); + assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest @@ -367,7 +376,9 @@ public void onComplete(Result result) public void testExpect100ContinueWithContentWithResponseFailureAfter100Continue(Transport transport) throws Exception { init(transport); - long idleTimeout = 100; + AtomicReference clientRequestRef = new AtomicReference<>(); + CountDownLatch clientLatch = new CountDownLatch(1); + CountDownLatch serverLatch = new CountDownLatch(1); scenario.startServer(new AbstractHandler() { @Override @@ -376,9 +387,12 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques baseRequest.setHandled(true); // Send 100-Continue and consume the content IO.copy(request.getInputStream(), new ByteArrayOutputStream()); + clientRequestRef.get().abort(new Exception("abort!")); try { - TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + if (!clientLatch.await(5, TimeUnit.SECONDS)) + throw new ServletException("Server timed out on client latch"); + serverLatch.countDown(); } catch (InterruptedException x) { @@ -386,11 +400,12 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques } } }); - scenario.startClient(httpClient -> httpClient.setIdleTimeout(idleTimeout)); + scenario.startClient(); byte[] content = new byte[1024]; - CountDownLatch latch = new CountDownLatch(1); - scenario.client.newRequest(scenario.newURI()) + org.eclipse.jetty.client.api.Request clientRequest = scenario.client.newRequest(scenario.newURI()); + clientRequestRef.set(clientRequest); + clientRequest .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) .body(new BytesRequestContent(content)) .send(new BufferingResponseListener() @@ -401,11 +416,12 @@ public void onComplete(Result result) assertTrue(result.isFailed()); assertNull(result.getRequestFailure()); assertNotNull(result.getResponseFailure()); - latch.countDown(); + clientLatch.countDown(); } }); - assertTrue(latch.await(3 * idleTimeout, TimeUnit.MILLISECONDS)); + assertTrue(clientLatch.await(5, TimeUnit.SECONDS)); + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest @@ -472,8 +488,14 @@ public void onComplete(Result result) @ArgumentsSource(TransportProvider.class) public void testExpect100ContinueWithDeferredContentRespond100Continue(Transport transport) throws Exception { + byte[] chunk1 = new byte[]{0, 1, 2, 3}; + byte[] chunk2 = new byte[]{4, 5, 6, 7}; + byte[] data = new byte[chunk1.length + chunk2.length]; + System.arraycopy(chunk1, 0, data, 0, chunk1.length); + System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); + + CountDownLatch serverLatch = new CountDownLatch(1); AtomicReference handlerThread = new AtomicReference<>(); - CountDownLatch demandLatch = new CountDownLatch(3); init(transport); scenario.start(new AbstractHandler() { @@ -483,26 +505,21 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques baseRequest.setHandled(true); handlerThread.set(Thread.currentThread()); // Send 100-Continue and echo the content - IO.copy(request.getInputStream(), response.getOutputStream()); + + ServletOutputStream outputStream = response.getOutputStream(); + DataInputStream inputStream = new DataInputStream(request.getInputStream()); + // Block until the 1st chunk is fully received. + byte[] buf1 = new byte[chunk1.length]; + inputStream.readFully(buf1); + outputStream.write(buf1); + + serverLatch.countDown(); + IO.copy(inputStream, outputStream); } }); - byte[] chunk1 = new byte[]{0, 1, 2, 3}; - byte[] chunk2 = new byte[]{4, 5, 6, 7}; - byte[] data = new byte[chunk1.length + chunk2.length]; - System.arraycopy(chunk1, 0, data, 0, chunk1.length); - System.arraycopy(chunk2, 0, data, chunk1.length, chunk2.length); - CountDownLatch requestLatch = new CountDownLatch(1); - AsyncRequestContent content = new AsyncRequestContent() - { - @Override - public void demand() - { - super.demand(); - demandLatch.countDown(); - } - }; + AsyncRequestContent content = new AsyncRequestContent(); scenario.client.newRequest(scenario.newURI()) .headers(headers -> headers.put(HttpHeader.EXPECT, HttpHeaderValue.CONTINUE)) .body(content) @@ -516,7 +533,7 @@ public void onComplete(Result result) } }); - // Wait for the handler thread to be blocked in IO. + // Wait for the handler thread to be blocked in the 1st IO. await().atMost(5, TimeUnit.SECONDS).until(() -> { Thread thread = handlerThread.get(); @@ -525,7 +542,13 @@ public void onComplete(Result result) content.offer(ByteBuffer.wrap(chunk1)); - assertTrue(demandLatch.await(5, TimeUnit.SECONDS)); + // Wait for the handler thread to be blocked in the 2nd IO. + assertTrue(serverLatch.await(5, TimeUnit.SECONDS)); + await().atMost(5, TimeUnit.SECONDS).until(() -> + { + Thread thread = handlerThread.get(); + return thread != null && thread.getState() == Thread.State.WAITING; + }); content.offer(ByteBuffer.wrap(chunk2)); content.close(); From 0a98a37cb6f7e7fb9059910058ea5ff46836a281 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 4 Aug 2021 10:57:09 +0200 Subject: [PATCH 11/24] #6327 wait for the request to be committed before sending the data Signed-off-by: Ludovic Orban --- .../http/client/HttpClientStreamTest.java | 21 +++++++------------ 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java index a4f3b8008955..6e49d148f4b5 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientStreamTest.java @@ -678,29 +678,22 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, } }); - CountDownLatch demandLatch = new CountDownLatch(2); - CountDownLatch requestLatch = new CountDownLatch(1); - try (AsyncRequestContent content = new AsyncRequestContent() - { - @Override - public void demand() - { - demandLatch.countDown(); - super.demand(); - } - }) + CountDownLatch requestSentLatch = new CountDownLatch(1); + CountDownLatch responseLatch = new CountDownLatch(1); + try (AsyncRequestContent content = new AsyncRequestContent()) { scenario.client.newRequest(scenario.newURI()) .scheme(scenario.getScheme()) .body(content) + .onRequestCommit((request) -> requestSentLatch.countDown()) .send(result -> { if (result.isSucceeded() && result.getResponse().getStatus() == 200) - requestLatch.countDown(); + responseLatch.countDown(); }); // Make sure we provide the content *after* the request has been "sent". - assertTrue(demandLatch.await(5, TimeUnit.SECONDS)); + assertTrue(requestSentLatch.await(5, TimeUnit.SECONDS)); try (ByteArrayInputStream input = new ByteArrayInputStream(new byte[1024])) { @@ -712,7 +705,7 @@ public void demand() } } } - assertTrue(requestLatch.await(5, TimeUnit.SECONDS)); + assertTrue(responseLatch.await(5, TimeUnit.SECONDS)); } @ParameterizedTest From 6881ee9a6acb4ff39816df54ab3f45ef48878bd6 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 4 Aug 2021 17:47:19 +0200 Subject: [PATCH 12/24] #6327 make SelectorManagerTest.testConnectTimeoutBeforeSuccessfulConnect always enabled, using latches and less wasteful timeouts Signed-off-by: Ludovic Orban --- .../org/eclipse/jetty/io/SelectorManagerTest.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java index 68eb13bb47eb..6e461ac83ad0 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java @@ -55,15 +55,15 @@ public void dispose() throws Exception } @Test - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception { ServerSocketChannel server = ServerSocketChannel.open(); server.bind(new InetSocketAddress("localhost", 0)); SocketAddress address = server.getLocalAddress(); - final AtomicLong timeoutConnection = new AtomicLong(); - final long connectTimeout = 1000; + CountDownLatch connectionFinishedLatch = new CountDownLatch(1); + AtomicLong timeoutConnection = new AtomicLong(); + long connectTimeout = 1000; SelectorManager selectorManager = new SelectorManager(executor, scheduler) { @Override @@ -88,6 +88,10 @@ protected boolean doFinishConnect(SelectableChannel channel) throws IOException { return false; } + finally + { + connectionFinishedLatch.countDown(); + } } @Override @@ -117,8 +121,7 @@ protected void connectionFailed(SelectableChannel channel, Throwable ex, Object SocketChannel client1 = SocketChannel.open(); client1.configureBlocking(false); client1.connect(address); - long timeout = connectTimeout * 2; - timeoutConnection.set(timeout); + timeoutConnection.set(connectTimeout * 110 / 100); final CountDownLatch latch1 = new CountDownLatch(1); selectorManager.connect(client1, new Callback() { @@ -132,7 +135,7 @@ public void failed(Throwable x) assertFalse(client1.isOpen()); // Wait for the first connect to finish, as the selector thread is waiting in finishConnect(). - Thread.sleep(timeout); + assertTrue(connectionFinishedLatch.await(5, TimeUnit.SECONDS)); // Verify that after the failure we can connect successfully. try (SocketChannel client2 = SocketChannel.open()) From 0108a197e8d8fba887678645a5568aad73916964 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Wed, 4 Aug 2021 17:57:02 +0200 Subject: [PATCH 13/24] #6327 enable disabled HttpConnectionLifecycleTest tests, using awaitility to remove wasteful sleeps Signed-off-by: Ludovic Orban --- jetty-client/pom.xml | 5 +++++ .../jetty/client/HttpConnectionLifecycleTest.java | 12 ++---------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/jetty-client/pom.xml b/jetty-client/pom.xml index 7317069eb0e0..67702b3a5777 100644 --- a/jetty-client/pom.xml +++ b/jetty-client/pom.xml @@ -132,6 +132,11 @@ ${project.version} test + + org.awaitility + awaitility + test + org.apache.kerby kerb-simplekdc diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java index a28631cfe3fc..b1ec697f01ef 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpConnectionLifecycleTest.java @@ -32,13 +32,12 @@ import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.logging.StacklessLogging; import org.eclipse.jetty.server.handler.AbstractHandler; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ArgumentsSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import static org.awaitility.Awaitility.await; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -213,8 +212,6 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testBadRequestWithSlowRequestRemovesConnection(Scenario scenario) throws Exception { start(scenario, new EmptyServerHandler()); @@ -423,8 +420,6 @@ public void onComplete(Result result) @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Exception { start(scenario, new EmptyServerHandler()); @@ -448,10 +443,7 @@ public void testIdleConnectionIsClosedOnRemoteClose(Scenario scenario) throws Ex connector.stop(); // Give the connection some time to process the remote close - TimeUnit.SECONDS.sleep(1); - - assertEquals(0, idleConnections.size()); - assertEquals(0, activeConnections.size()); + await().atMost(5, TimeUnit.SECONDS).until(() -> idleConnections.size() == 0 && activeConnections.size() == 0); } @ParameterizedTest From 1a15f2c48d65c177e99064483bfe584ae7f6db3a Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 5 Aug 2021 09:28:28 +0200 Subject: [PATCH 14/24] #6327 enable more disabled tests, using awaitility and shortening wasteful sleeps Signed-off-by: Ludovic Orban --- .../eclipse/jetty/client/http/HttpSenderOverHTTPTest.java | 5 ++--- .../java/org/eclipse/jetty/fcgi/server/HttpClientTest.java | 6 ++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java index 1712e73a5065..8ec4f83dd194 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/http/HttpSenderOverHTTPTest.java @@ -35,8 +35,8 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; +import static org.awaitility.Awaitility.await; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -91,7 +91,6 @@ public void onSuccess(Request request) } @Test - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testSendNoRequestContentIncompleteFlush() throws Exception { ByteArrayEndPoint endPoint = new ByteArrayEndPoint("", 16); @@ -105,7 +104,7 @@ public void testSendNoRequestContentIncompleteFlush() throws Exception StringBuilder builder = new StringBuilder(endPoint.takeOutputString()); // Wait for the write to complete - TimeUnit.SECONDS.sleep(1); + await().atMost(5, TimeUnit.SECONDS).until(() -> endPoint.toEndPointString().contains(",flush=P,")); String chunk = endPoint.takeOutputString(); while (chunk.length() > 0) diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index b45ac747af6b..3216fca1b454 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -48,7 +48,6 @@ import org.eclipse.jetty.util.Callback; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -411,7 +410,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, } @Test - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testRequestIdleTimeout() throws Exception { final long idleTimeout = 1000; @@ -423,7 +421,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, try { baseRequest.setHandled(true); - TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); + TimeUnit.MILLISECONDS.sleep(idleTimeout); } catch (InterruptedException x) { @@ -437,7 +435,7 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, assertThrows(TimeoutException.class, () -> client.newRequest(host, port) .scheme(scheme) - .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) + .idleTimeout(idleTimeout * 90 / 100, TimeUnit.MILLISECONDS) .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) .send()); From c818581185c6f3169a836f4bf882e2e5301cb7ae Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 5 Aug 2021 16:33:12 +0200 Subject: [PATCH 15/24] #6327 more latches, less sleeps Signed-off-by: Ludovic Orban --- .../eclipse/jetty/io/SelectorManagerTest.java | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java index 6e461ac83ad0..d93d9dfb6531 100644 --- a/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java +++ b/jetty-io/src/test/java/org/eclipse/jetty/io/SelectorManagerTest.java @@ -22,7 +22,6 @@ import java.nio.channels.SocketChannel; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicLong; import org.eclipse.jetty.util.Callback; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -30,7 +29,6 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -62,16 +60,14 @@ public void testConnectTimeoutBeforeSuccessfulConnect() throws Exception SocketAddress address = server.getLocalAddress(); CountDownLatch connectionFinishedLatch = new CountDownLatch(1); - AtomicLong timeoutConnection = new AtomicLong(); + CountDownLatch failedConnectionLatch = new CountDownLatch(1); long connectTimeout = 1000; SelectorManager selectorManager = new SelectorManager(executor, scheduler) { @Override protected EndPoint newEndPoint(SelectableChannel channel, ManagedSelector selector, SelectionKey key) { - SocketChannelEndPoint endPoint = new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler()); - endPoint.setIdleTimeout(connectTimeout / 2); - return endPoint; + return new SocketChannelEndPoint((SocketChannel)channel, selector, key, getScheduler()); } @Override @@ -79,9 +75,7 @@ protected boolean doFinishConnect(SelectableChannel channel) throws IOException { try { - long timeout = timeoutConnection.get(); - if (timeout > 0) - TimeUnit.MILLISECONDS.sleep(timeout); + assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS)); return super.doFinishConnect(channel); } catch (InterruptedException e) @@ -120,39 +114,36 @@ protected void connectionFailed(SelectableChannel channel, Throwable ex, Object { SocketChannel client1 = SocketChannel.open(); client1.configureBlocking(false); - client1.connect(address); - timeoutConnection.set(connectTimeout * 110 / 100); - final CountDownLatch latch1 = new CountDownLatch(1); + assertFalse(client1.connect(address)); selectorManager.connect(client1, new Callback() { @Override public void failed(Throwable x) { - latch1.countDown(); + failedConnectionLatch.countDown(); } }); - assertTrue(latch1.await(connectTimeout * 3, TimeUnit.MILLISECONDS)); + assertTrue(failedConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS)); assertFalse(client1.isOpen()); - // Wait for the first connect to finish, as the selector thread is waiting in finishConnect(). + // Wait for the first connect to finish, as the selector thread is waiting in doFinishConnect(). assertTrue(connectionFinishedLatch.await(5, TimeUnit.SECONDS)); // Verify that after the failure we can connect successfully. try (SocketChannel client2 = SocketChannel.open()) { client2.configureBlocking(false); - client2.connect(address); - timeoutConnection.set(0); - final CountDownLatch latch2 = new CountDownLatch(1); + assertFalse(client2.connect(address)); + CountDownLatch successfulConnectionLatch = new CountDownLatch(1); selectorManager.connect(client2, new Callback() { @Override public void succeeded() { - latch2.countDown(); + successfulConnectionLatch.countDown(); } }); - assertTrue(latch2.await(connectTimeout * 5, TimeUnit.MILLISECONDS)); + assertTrue(successfulConnectionLatch.await(connectTimeout * 2, TimeUnit.MILLISECONDS)); assertTrue(client2.isOpen()); } } From 15bef0c9ed9794719bee439bb858da519af7e2f7 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Thu, 5 Aug 2021 16:34:00 +0200 Subject: [PATCH 16/24] #6327 rework testRequestIdleTimeout and merge http(s) and fcgi test Signed-off-by: Ludovic Orban --- .../eclipse/jetty/client/HttpClientTest.java | 43 --------------- .../jetty/fcgi/server/HttpClientTest.java | 41 -------------- .../jetty/http/client/HttpClientTest.java | 54 +++++++++++++++++++ 3 files changed, 54 insertions(+), 84 deletions(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index 7604ae1a28cb..bc6b1c39ed5c 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -40,7 +40,6 @@ import java.util.concurrent.Exchanger; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; @@ -49,7 +48,6 @@ import javax.servlet.AsyncContext; import javax.servlet.DispatcherType; import javax.servlet.ReadListener; -import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; @@ -687,47 +685,6 @@ public void onComplete(Result result) assertTrue(latch.await(5, TimeUnit.SECONDS)); } - @ParameterizedTest - @ArgumentsSource(ScenarioProvider.class) - public void testRequestIdleTimeout(Scenario scenario) throws Exception - { - long idleTimeout = 100; - start(scenario, new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException - { - try - { - baseRequest.setHandled(true); - TimeUnit.MILLISECONDS.sleep(2 * idleTimeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } - } - }); - - String host = "localhost"; - int port = connector.getLocalPort(); - assertThrows(TimeoutException.class, () -> - client.newRequest(host, port) - .scheme(scenario.getScheme()) - .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) - .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) - .send()); - - // Make another request without specifying the idle timeout, should not fail - ContentResponse response = client.newRequest(host, port) - .scheme(scenario.getScheme()) - .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) - .send(); - - assertNotNull(response); - assertEquals(200, response.getStatus()); - } - @ParameterizedTest @ArgumentsSource(ScenarioProvider.class) public void testSendToIPv6Address(Scenario scenario) throws Exception diff --git a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java index 3216fca1b454..fee6a3d2617c 100644 --- a/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java +++ b/jetty-fcgi/fcgi-server/src/test/java/org/eclipse/jetty/fcgi/server/HttpClientTest.java @@ -24,7 +24,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.zip.GZIPOutputStream; @@ -409,46 +408,6 @@ public void handle(String target, org.eclipse.jetty.server.Request baseRequest, assertArrayEquals(data, response.getContent()); } - @Test - public void testRequestIdleTimeout() throws Exception - { - final long idleTimeout = 1000; - start(new AbstractHandler() - { - @Override - public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException - { - try - { - baseRequest.setHandled(true); - TimeUnit.MILLISECONDS.sleep(idleTimeout); - } - catch (InterruptedException x) - { - throw new ServletException(x); - } - } - }); - - final String host = "localhost"; - final int port = connector.getLocalPort(); - assertThrows(TimeoutException.class, () -> - client.newRequest(host, port) - .scheme(scheme) - .idleTimeout(idleTimeout * 90 / 100, TimeUnit.MILLISECONDS) - .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) - .send()); - - // Make another request without specifying the idle timeout, should not fail - ContentResponse response = client.newRequest(host, port) - .scheme(scheme) - .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) - .send(); - - assertNotNull(response); - assertEquals(200, response.getStatus()); - } - @Test public void testConnectionIdleTimeout() throws Exception { diff --git a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java index 4bb62d62dafe..92522a15da10 100644 --- a/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java +++ b/tests/test-http-client-transport/src/test/java/org/eclipse/jetty/http/client/HttpClientTest.java @@ -22,9 +22,11 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.IntStream; +import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; @@ -781,6 +783,58 @@ protected void service(String target, Request jettyRequest, HttpServletRequest r assertTrue(resultLatch.await(5, TimeUnit.SECONDS)); } + @ParameterizedTest + @ArgumentsSource(TransportProvider.class) + public void testRequestIdleTimeout(Transport transport) throws Exception + { + init(transport); + + CountDownLatch latch = new CountDownLatch(1); + long idleTimeout = 500; + scenario.start(new AbstractHandler() + { + @Override + public void handle(String target, org.eclipse.jetty.server.Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws ServletException + { + try + { + baseRequest.setHandled(true); + if (target.equals("/1")) + assertTrue(latch.await(5, TimeUnit.SECONDS)); + else if (target.equals("/2")) + Thread.sleep(2 * idleTimeout); + else + fail("Unknown path: " + target); + } + catch (InterruptedException x) + { + throw new ServletException(x); + } + } + }); + + String host = "localhost"; + int port = scenario.getNetworkConnectorLocalPortInt().get(); + assertThrows(TimeoutException.class, () -> + scenario.client.newRequest(host, port) + .scheme(scenario.getScheme()) + .path("/1") + .idleTimeout(idleTimeout, TimeUnit.MILLISECONDS) + .timeout(2 * idleTimeout, TimeUnit.MILLISECONDS) + .send()); + latch.countDown(); + + // Make another request without specifying the idle timeout, should not fail + ContentResponse response = scenario.client.newRequest(host, port) + .scheme(scenario.getScheme()) + .path("/2") + .timeout(3 * idleTimeout, TimeUnit.MILLISECONDS) + .send(); + + assertNotNull(response); + assertEquals(200, response.getStatus()); + } + private void sleep(long time) throws IOException { try From a99ff502d14cdd4bd8c7d7d9aad2d5534baf2737 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Fri, 6 Aug 2021 11:15:58 +0200 Subject: [PATCH 17/24] #6327 enable more disabled tests Signed-off-by: Ludovic Orban --- .../jetty/server/ConnectionOpenCloseTest.java | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java index 4f84a6d3c8a3..5af54a444505 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ConnectionOpenCloseTest.java @@ -34,9 +34,7 @@ import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.hamcrest.Matchers; -import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -46,8 +44,6 @@ public class ConnectionOpenCloseTest extends AbstractHttpTest { @Test - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testOpenClose() throws Exception { server.setHandler(new AbstractHandler() @@ -97,8 +93,6 @@ public void onClosed(Connection connection) } @Test - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testOpenRequestClose() throws Exception { server.setHandler(new AbstractHandler() @@ -153,15 +147,13 @@ public void onClosed(Connection connection) assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); // Wait some time to see if the callbacks are called too many times - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(200); assertEquals(2, callbacks.get()); } } @Test - @Tag("Slow") - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testSSLOpenRequestClose() throws Exception { SslContextFactory.Server sslContextFactory = new SslContextFactory.Server(); @@ -223,7 +215,7 @@ public void onClosed(Connection connection) assertTrue(closeLatch.await(5, TimeUnit.SECONDS)); // Wait some time to see if the callbacks are called too many times - TimeUnit.SECONDS.sleep(1); + TimeUnit.MILLISECONDS.sleep(200); assertEquals(4, callbacks.get()); } From 324fe5e3ee97c76d0db6125cfdedaf1008269196 Mon Sep 17 00:00:00 2001 From: Ludovic Orban Date: Mon, 9 Aug 2021 13:48:12 +0200 Subject: [PATCH 18/24] #6327 enable testDefaultServletSuccess Signed-off-by: Ludovic Orban --- .../eclipse/jetty/servlets/ThreadStarvationTest.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java index 729a00e13acc..18b0ba1a15ee 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/ThreadStarvationTest.java @@ -54,7 +54,6 @@ import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.condition.DisabledIfSystemProperty; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -71,10 +70,9 @@ public void dispose() throws Exception } @Test - @DisabledIfSystemProperty(named = "env", matches = "ci") // TODO: SLOW, needs review public void testDefaultServletSuccess() throws Exception { - int maxThreads = 10; + int maxThreads = 6; QueuedThreadPool threadPool = new QueuedThreadPool(maxThreads, maxThreads); threadPool.setDetailedDump(true); _server = new Server(threadPool); @@ -86,11 +84,11 @@ public void testDefaultServletSuccess() throws Exception Path resourcePath = Paths.get(directory.getPath(), resourceName); try (OutputStream output = Files.newOutputStream(resourcePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE)) { - byte[] chunk = new byte[1024]; + byte[] chunk = new byte[256 * 1024]; Arrays.fill(chunk, (byte)'X'); chunk[chunk.length - 2] = '\r'; chunk[chunk.length - 1] = '\n'; - for (int i = 0; i < 256 * 1024; ++i) + for (int i = 0; i < 1024; ++i) { output.write(chunk); } @@ -135,10 +133,9 @@ protected void onIncompleteFlush() "\r\n"; output.write(request.getBytes(StandardCharsets.UTF_8)); output.flush(); - Thread.sleep(100); } - // Wait for a the servlet to block. + // Wait for a thread on the servlet to block. assertTrue(writePending.await(5, TimeUnit.SECONDS)); long expected = Files.size(resourcePath); From 4af93b5e19120d0b1ccbca2a9e5269871f4635cd Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 13 Aug 2021 17:32:53 +0200 Subject: [PATCH 19/24] Issue #6476 - warn on exec (#6597) Fixes #6476 - Show message if JVM args are present but new JVM is spawned * Improved documentation by correctly redacting out `jetty-halt.xml`, an XML file that is only necessary for rendering the documentation. * Added WARN message when new JVM is spawned. * Updated documentation. * Updated --list-config to report whether a JVM is forked. * Added test case. Signed-off-by: Simone Bordet --- .../jetty/docs/JettyIncludeExtension.java | 11 +++++- .../operations-guide/modules/modules.adoc | 2 + .../start/start-configure.adoc | 38 +++++++++++++++++-- .../java/org/eclipse/jetty/start/Main.java | 10 +++++ .../org/eclipse/jetty/start/StartArgs.java | 16 +++----- .../tests/distribution/DistributionTests.java | 33 ++++++++++++++++ 6 files changed, 94 insertions(+), 16 deletions(-) diff --git a/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java b/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java index f9ace37ef6d7..903f6920a5eb 100644 --- a/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java +++ b/documentation/jetty-asciidoctor-extensions/src/main/java/org/eclipse/jetty/docs/JettyIncludeExtension.java @@ -144,6 +144,7 @@ private String captureOutput(Document document, Map attributes, .map(line -> redact(line, run.getConfig().getMavenLocalRepository(), "/path/to/maven.repository")) .map(line -> redact(line, run.getConfig().getJettyHome().toString(), "/path/to/jetty.home")) .map(line -> redact(line, run.getConfig().getJettyBase().toString(), "/path/to/jetty.base")) + .map(line -> regexpRedact(line, "(^| )[^ ]+/etc/jetty-halt\\.xml", "")) .map(line -> redact(line, (String)document.getAttribute("project-version"), (String)document.getAttribute("version"))); lines = replace(lines, (String)attributes.get("replace")); lines = delete(lines, (String)attributes.get("delete")); @@ -160,6 +161,13 @@ private String redact(String line, String target, String replacement) return line; } + private String regexpRedact(String line, String regexp, String replacement) + { + if (regexp != null && replacement != null) + return line.replaceAll(regexp, replacement); + return line; + } + private Stream replace(Stream lines, String replace) { if (replace == null) @@ -178,8 +186,7 @@ private Stream delete(Stream lines, String delete) if (delete == null) return lines; Pattern regExp = Pattern.compile(delete); - return lines.filter(line -> !regExp.matcher(line).find()) - .filter(line -> !line.contains("jetty-halt.xml")); + return lines.filter(line -> !regExp.matcher(line).find()); } private Stream denoteLineStart(Stream lines) diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc index 67c414717672..2b020bac4bb2 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/modules/modules.adoc @@ -396,6 +396,8 @@ When the `[exec]` section is present, the JVM running the Jetty start mechanism This is necessary because JVM options such as `-Xmx` (that specifies the max JVM heap size) cannot be changed in a running JVM. For an example, see xref:og-start-configure-custom-module-exec[this section]. +You can avoid that the Jetty start mechanism forks the second JVM, as explained in xref:og-start-configure-dry-run[this section]. + [[og-modules-directive-jpms]] ===== [jpms] diff --git a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc index c6814ce45d63..e711d9018eae 100644 --- a/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc +++ b/documentation/jetty-documentation/src/main/asciidoc/operations-guide/start/start-configure.adoc @@ -163,10 +163,15 @@ $ java -jar $JETTY_HOME/start.jar --add-modules=jvm Since the module defines an `[exec]` section, it will fork _another_ JVM when Jetty is started. -This means that when you start Jetty, there will be _two_ JVMs running: one spawned by you when you run `java -jar $JETTY_HOME/start.jar`, and another spawned by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM). +This means that when you start Jetty, there will be _two_ JVMs running: one created by you when you run `java -jar $JETTY_HOME/start.jar`, and another forked by the Jetty start mechanism with the JVM options you specified (that cannot be applied to an already running JVM). Again, you can xref:og-start-configure-dry-run[display the JVM command line] to verify that it is correct. +[TIP] +==== +The second JVM forked by the Jetty start mechanism when one of the modules requires forking, for example a module that contains an `[exec]` section, may not be desirable, and may be avoided as explained in xref:og-start-configure-dry-run[this section]. +==== + [[og-start-configure-display]] ===== Displaying the Configuration @@ -205,7 +210,28 @@ Some option, such as `--jpms`, imply `--exec`, as it won't be possible to modify To start Jetty without forking a second JVM, the `--dry-run` option can be used to generate a command line that is then executed so that starting Jetty only spawns one JVM. -The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to generate scripts or to create an arguments file that can be passed to the `java` executable. +IMPORTANT: You can use the `--dry-run` option as explained below to avoid forking a second JVM when using modules that have the `[exec]` section, or the `--exec` option, or when using the `--jpms` option. + +For example, using the `--dry-run` option with the `jvm.mod` introduced in xref:og-start-configure-custom-module-exec[this section] produces the following command line: + +---- +$ java -jar $JETTY_HOME/start.jar --dry-run +---- + +[source,options=nowrap] +---- +include::jetty[setupModules="src/main/asciidoc/operations-guide/start/jvm.mod",setupArgs="--add-modules=http,jvm",args="--dry-run",replace="( ),$1\\\n"] +---- + +You can then run the generated command line. + +For example, in the Linux `bash` shell you can run it by wrapping it into `$(\...)`: + +---- +$ $(java -jar $JETTY_HOME/start.jar --dry-run) +---- + +The `--dry-run` option is quite flexible and below you can find a few examples of how to use it to avoid forking a second JVM, or generating scripts or creating an arguments file that can be passed to (a possibly alternative) `java` executable. To display the `java` executable used to start Jetty: @@ -304,7 +330,13 @@ $ java -jar $JETTY_HOME/start.jar --dry-run=##opts,path,main,args## > /tmp/jvm_c $ /some/other/java @/tmp/jvm_cmd_line.txt ---- -Alternatively, they can be combined in a shell script: +Using `--dry-run=opts,path,main,args` can be used to avoid that the Jetty start mechanism forks a second JVM when using modules that require forking: + +---- +$ java $(java -jar $JETTY_HOME/start.jar --dry-run=opts,path,main,args) +---- + +The output of different `--dry-run` executions can be creatively combined in a shell script: [source,subs=quotes] ---- diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java index ac56b37ea50e..82c7ad3df4a5 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/Main.java @@ -30,6 +30,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Locale; +import java.util.stream.Collectors; import org.eclipse.jetty.start.Props.Prop; import org.eclipse.jetty.start.config.CommandLineConfigSource; @@ -467,6 +468,15 @@ else if (args.isCreateFiles() || !args.getStartModules().isEmpty()) { CommandLineBuilder cmd = args.getMainArgs(StartArgs.ALL_PARTS); cmd.debug(); + + List execModules = args.getEnabledModules().stream() + .map(name -> args.getAllModules().get(name)) + // Keep only the forking modules. + .filter(module -> !module.getJvmArgs().isEmpty()) + .map(Module::getName) + .collect(Collectors.toList()); + StartLog.warn("Forking second JVM due to forking module(s): %s. Use --dry-run to generate the command line to avoid forking.", execModules); + ProcessBuilder pbuilder = new ProcessBuilder(cmd.getArgs()); StartLog.endStartLog(); final Process process = pbuilder.start(); diff --git a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java index b9d3fb0298d2..812610fc1998 100644 --- a/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java +++ b/jetty-start/src/main/java/org/eclipse/jetty/start/StartArgs.java @@ -302,7 +302,7 @@ public void dumpEnvironment() // Jetty Environment System.out.println(); System.out.println("Jetty Environment:"); - System.out.println("-----------------"); + System.out.println("------------------"); dumpProperty(JETTY_VERSION_KEY); dumpProperty(JETTY_TAG_NAME_KEY); dumpProperty(JETTY_BUILDNUM_KEY); @@ -330,26 +330,20 @@ public void dumpEnvironment() public void dumpJvmArgs() { - System.out.println(); - System.out.println("JVM Arguments:"); - System.out.println("--------------"); if (jvmArgs.isEmpty()) - { - System.out.println(" (no jvm args specified)"); return; - } + + System.out.println(); + System.out.println("Forked JVM Arguments:"); + System.out.println("---------------------"); for (String jvmArgKey : jvmArgs) { String value = System.getProperty(jvmArgKey); if (value != null) - { System.out.printf(" %s = %s%n", jvmArgKey, value); - } else - { System.out.printf(" %s%n", jvmArgKey); - } } } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 089d539fe79b..41129f76dcb7 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -931,4 +931,37 @@ public void testUnixDomain() throws Exception } } } + + @Test + public void testModuleWithExecEmitsWarning() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + JettyHomeTester distribution = JettyHomeTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + Path jettyBase = distribution.getJettyBase(); + Path jettyBaseModules = jettyBase.resolve("modules"); + Files.createDirectories(jettyBaseModules); + Path execModule = jettyBaseModules.resolve("exec.mod"); + String module = "" + + "[exec]\n" + + "--show-version"; + Files.write(execModule, List.of(module), StandardOpenOption.CREATE); + + try (JettyHomeTester.Run run1 = distribution.start(List.of("--add-modules=http,exec"))) + { + assertTrue(run1.awaitFor(10, TimeUnit.SECONDS)); + assertEquals(0, run1.getExitValue()); + + int port = distribution.freePort(); + try (JettyHomeTester.Run run2 = distribution.start("jetty.http.port=" + port)) + { + assertTrue(run2.awaitConsoleLogsFor("Started Server@", 10, TimeUnit.SECONDS)); + assertTrue(run2.getLogs().stream() + .anyMatch(log -> log.contains("WARN") && log.contains("Forking"))); + } + } + } } From 2b0161e743ac4f536e2017148551487624c367d1 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Sat, 14 Aug 2021 01:37:51 +1000 Subject: [PATCH 20/24] Fix #6476 Use dry run in jetty.sh (#6598) * Fix #6597 Use dry run in jetty.sh Use a --dry-run in jetty.sh to pre-expand the java arguments and thus avoid having two JVMs running in the case of exec. Also made a small change to allow script to check the current directory for JETTY_BASE, as that allows testing and runs in the same style as direct calls to start.jar Signed-off-by: Greg Wilkins Co-authored-by: Simone Bordet --- jetty-home/src/main/resources/bin/jetty.sh | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/jetty-home/src/main/resources/bin/jetty.sh b/jetty-home/src/main/resources/bin/jetty.sh index c128bbad2ce7..3a429107d13a 100755 --- a/jetty-home/src/main/resources/bin/jetty.sh +++ b/jetty-home/src/main/resources/bin/jetty.sh @@ -66,7 +66,8 @@ NAME=$(echo $(basename $0) | sed -e 's/^[SK][0-9]*//' -e 's/\.sh$//') # /webapps/jetty.war # # JETTY_BASE -# Where your Jetty base directory is. If not set, the value from +# Where your Jetty base directory is. If not set, then the currently +# directory is checked, otherwise the value from # $JETTY_HOME will be used. # # JETTY_RUN @@ -238,7 +239,6 @@ then fi fi - ################################################## # No JETTY_HOME yet? We're out of luck! ################################################## @@ -247,20 +247,23 @@ if [ -z "$JETTY_HOME" ]; then exit 1 fi +RUN_DIR=$(pwd) cd "$JETTY_HOME" -JETTY_HOME=$PWD - +JETTY_HOME=$(pwd) ################################################## # Set JETTY_BASE ################################################## +export JETTY_BASE if [ -z "$JETTY_BASE" ]; then - JETTY_BASE=$JETTY_HOME + if [ -d "$RUN_DIR/start.d" -o -f "$RUN_DIR/start.ini" ]; then + JETTY_BASE=$RUN_DIR + else + JETTY_BASE=$JETTY_HOME + fi fi - cd "$JETTY_BASE" -JETTY_BASE=$PWD - +JETTY_BASE=$(pwd) ##################################################### # Check that jetty is where we think it is @@ -430,7 +433,7 @@ case "`uname`" in CYGWIN*) JETTY_START="`cygpath -w $JETTY_START`";; esac -RUN_ARGS=(${JAVA_OPTIONS[@]} -jar "$JETTY_START" ${JETTY_ARGS[*]}) +RUN_ARGS=$(echo $JAVA_OPTIONS ; "$JAVA" -jar "$JETTY_START" --dry-run=opts,path,main,args ${JETTY_ARGS[*]}) RUN_CMD=("$JAVA" ${RUN_ARGS[@]}) ##################################################### From dbc0ce7c134ce255ad8beae286f9707d46ab2e18 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Fri, 13 Aug 2021 17:39:52 +0200 Subject: [PATCH 21/24] Fixes #6372 - Review socket options configuration (#6610) * Fixes #6372 - Review socket options configuration Introduced in ClientConnector: * tcpNoDelay * reusePort * receiveBufferSize * sendBufferSize Reworked configuration of socket options in ClientConnector. JMX-ified ClientConnector. Introduced reusePort in ServerConnector. Updated server modules with the new reusePort property. Signed-off-by: Simone Bordet --- .../org/eclipse/jetty/client/HttpClient.java | 4 + .../eclipse/jetty/client/HttpClientTest.java | 40 ++++ .../http/HttpClientTransportOverHTTP2.java | 2 + .../org/eclipse/jetty/io/ClientConnector.java | 188 ++++++++++++++++-- .../src/main/config/etc/jetty-http.xml | 1 + .../src/main/config/etc/jetty-ssl.xml | 1 + jetty-server/src/main/config/modules/http.mod | 3 + jetty-server/src/main/config/modules/ssl.mod | 3 + .../eclipse/jetty/server/ServerConnector.java | 28 ++- .../jetty/server/ServerConnectorTest.java | 58 ++++++ 10 files changed, 307 insertions(+), 21 deletions(-) diff --git a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java index 76caef9bb3d1..8eb987c34946 100644 --- a/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java +++ b/jetty-client/src/main/java/org/eclipse/jetty/client/HttpClient.java @@ -923,8 +923,10 @@ public void setMaxRedirects(int maxRedirects) /** * @return whether TCP_NODELAY is enabled + * @deprecated use {@link ClientConnector#isTCPNoDelay()} instead */ @ManagedAttribute(value = "Whether the TCP_NODELAY option is enabled", name = "tcpNoDelay") + @Deprecated public boolean isTCPNoDelay() { return tcpNoDelay; @@ -933,7 +935,9 @@ public boolean isTCPNoDelay() /** * @param tcpNoDelay whether TCP_NODELAY is enabled * @see java.net.Socket#setTcpNoDelay(boolean) + * @deprecated use {@link ClientConnector#setTCPNoDelay(boolean)} instead */ + @Deprecated public void setTCPNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java index bc6b1c39ed5c..ab46fcdb2505 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTest.java @@ -73,6 +73,7 @@ import org.eclipse.jetty.http.HttpHeaderValue; import org.eclipse.jetty.http.HttpMethod; import org.eclipse.jetty.http.HttpScheme; +import org.eclipse.jetty.http.HttpStatus; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.io.ClientConnector; import org.eclipse.jetty.io.EndPoint; @@ -1909,6 +1910,45 @@ public long getLength() assertTrue(serverOnErrorLatch.await(5, TimeUnit.SECONDS), "serverOnErrorLatch didn't finish"); } + @ParameterizedTest + @ArgumentsSource(ScenarioProvider.class) + public void testBindAddress(Scenario scenario) throws Exception + { + String bindAddress = "127.0.0.2"; + start(scenario, new EmptyServerHandler() + { + @Override + protected void service(String target, org.eclipse.jetty.server.Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + assertEquals(bindAddress, request.getRemoteAddr()); + } + }); + + client.setBindAddress(new InetSocketAddress(bindAddress, 0)); + + CountDownLatch latch = new CountDownLatch(1); + ContentResponse response = client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .path("/1") + .onRequestBegin(r -> + { + client.newRequest("localhost", connector.getLocalPort()) + .scheme(scenario.getScheme()) + .path("/2") + .send(result -> + { + assertTrue(result.isSucceeded()); + assertEquals(HttpStatus.OK_200, result.getResponse().getStatus()); + latch.countDown(); + }); + }) + .timeout(5, TimeUnit.SECONDS) + .send(); + + assertTrue(latch.await(5, TimeUnit.SECONDS)); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + private void assertCopyRequest(Request original) { Request copy = client.copyRequest((HttpRequest)original, original.getURI()); diff --git a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java index f316cbedb0eb..001584b3a1f1 100644 --- a/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java +++ b/jetty-http2/http2-http-client-transport/src/main/java/org/eclipse/jetty/http2/client/http/HttpClientTransportOverHTTP2.java @@ -93,6 +93,8 @@ protected void doStart() throws Exception client.setInputBufferSize(httpClient.getResponseBufferSize()); client.setUseInputDirectByteBuffers(httpClient.isUseInputDirectByteBuffers()); client.setUseOutputDirectByteBuffers(httpClient.isUseOutputDirectByteBuffers()); + client.setConnectBlocking(httpClient.isConnectBlocking()); + client.setBindAddress(httpClient.getBindAddress()); } addBean(client); super.doStart(); diff --git a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java index f0a4c987e17f..414db0623366 100644 --- a/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java +++ b/jetty-io/src/main/java/org/eclipse/jetty/io/ClientConnector.java @@ -18,6 +18,7 @@ import java.net.ProtocolFamily; import java.net.SocketAddress; import java.net.SocketException; +import java.net.SocketOption; import java.net.StandardProtocolFamily; import java.net.StandardSocketOptions; import java.nio.channels.SelectableChannel; @@ -33,6 +34,8 @@ import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.JavaVersion; import org.eclipse.jetty.util.Promise; +import org.eclipse.jetty.util.annotation.ManagedAttribute; +import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.component.ContainerLifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -41,6 +44,32 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +/** + *

The client-side component that connects to server sockets.

+ *

ClientConnector delegates the handling of {@link SocketChannel}s + * to a {@link SelectorManager}, and centralizes the configuration of + * necessary components such as the executor, the scheduler, etc.

+ *

ClientConnector offers a low-level API that can be used to + * connect {@link SocketChannel}s to listening servers via the + * {@link #connect(SocketAddress, Map)} method.

+ *

However, a ClientConnector instance is typically just configured + * and then passed to an HttpClient transport, so that applications + * can use high-level APIs to make HTTP requests to servers:

+ *
+ * // Create a ClientConnector instance.
+ * ClientConnector connector = new ClientConnector();
+ *
+ * // Configure the ClientConnector.
+ * connector.setSelectors(1);
+ * connector.setSslContextFactory(new SslContextFactory.Client());
+ *
+ * // Pass it to the HttpClient transport.
+ * HttpClientTransport transport = new HttpClientTransportDynamic(connector);
+ * HttpClient httpClient = new HttpClient(transport);
+ * httpClient.start();
+ * 
+ */ +@ManagedObject public class ClientConnector extends ContainerLifeCycle { public static final String CLIENT_CONNECTOR_CONTEXT_KEY = "org.eclipse.jetty.client.connector"; @@ -49,6 +78,12 @@ public class ClientConnector extends ContainerLifeCycle public static final String CONNECTION_PROMISE_CONTEXT_KEY = CLIENT_CONNECTOR_CONTEXT_KEY + ".connectionPromise"; private static final Logger LOG = LoggerFactory.getLogger(ClientConnector.class); + /** + *

Creates a ClientConnector configured to connect via Unix-Domain sockets to the given Unix-Domain path

+ * + * @param path the Unix-Domain path to connect to + * @return a ClientConnector that connects to the given Unix-Domain path + */ public static ClientConnector forUnixDomain(Path path) { return new ClientConnector(SocketChannelWithAddress.Factory.forUnixDomain(path)); @@ -65,7 +100,11 @@ public static ClientConnector forUnixDomain(Path path) private Duration connectTimeout = Duration.ofSeconds(5); private Duration idleTimeout = Duration.ofSeconds(30); private SocketAddress bindAddress; + private boolean tcpNoDelay = true; private boolean reuseAddress = true; + private boolean reusePort; + private int receiveBufferSize = -1; + private int sendBufferSize = -1; public ClientConnector() { @@ -129,6 +168,10 @@ public void setSslContextFactory(SslContextFactory.Client sslContextFactory) this.sslContextFactory = sslContextFactory; } + /** + * @return the number of NIO selectors + */ + @ManagedAttribute("The number of NIO selectors") public int getSelectors() { return selectors; @@ -141,6 +184,10 @@ public void setSelectors(int selectors) this.selectors = selectors; } + /** + * @return whether {@link #connect(SocketAddress, Map)} operations are performed in blocking mode + */ + @ManagedAttribute("Whether connect operations are performed in blocking mode") public boolean isConnectBlocking() { return connectBlocking; @@ -151,6 +198,10 @@ public void setConnectBlocking(boolean connectBlocking) this.connectBlocking = connectBlocking; } + /** + * @return the timeout of {@link #connect(SocketAddress, Map)} operations + */ + @ManagedAttribute("The timeout of connect operations") public Duration getConnectTimeout() { return connectTimeout; @@ -163,6 +214,10 @@ public void setConnectTimeout(Duration connectTimeout) selectorManager.setConnectTimeout(connectTimeout.toMillis()); } + /** + * @return the max duration for which a connection can be idle (that is, without traffic of bytes in either direction) + */ + @ManagedAttribute("The duration for which a connection can be idle") public Duration getIdleTimeout() { return idleTimeout; @@ -173,26 +228,120 @@ public void setIdleTimeout(Duration idleTimeout) this.idleTimeout = idleTimeout; } + /** + * @return the address to bind a socket to before the connect operation + */ + @ManagedAttribute("The socket address to bind sockets to before the connect operation") public SocketAddress getBindAddress() { return bindAddress; } + /** + *

Sets the bind address of sockets before the connect operation.

+ *

In multi-homed hosts, you may want to connect from a specific address:

+ *
+     * clientConnector.setBindAddress(new InetSocketAddress("127.0.0.2", 0));
+     * 
+ *

Note the use of the port {@code 0} to indicate that a different ephemeral port + * should be used for each different connection.

+ *

In the rare cases where you want to use the same port for all connections, + * you must also call {@link #setReusePort(boolean) setReusePort(true)}.

+ * + * @param bindAddress the socket address to bind to before the connect operation + */ public void setBindAddress(SocketAddress bindAddress) { this.bindAddress = bindAddress; } + /** + * @return whether small TCP packets are sent without delay + */ + @ManagedAttribute("Whether small TCP packets are sent without delay") + public boolean isTCPNoDelay() + { + return tcpNoDelay; + } + + public void setTCPNoDelay(boolean tcpNoDelay) + { + this.tcpNoDelay = tcpNoDelay; + } + + /** + * @return whether rebinding is allowed with sockets in tear-down states + */ + @ManagedAttribute("Whether rebinding is allowed with sockets in tear-down states") public boolean getReuseAddress() { return reuseAddress; } + /** + *

Sets whether it is allowed to bind a socket to a socket address + * that may be in use by another socket in tear-down state, for example + * in TIME_WAIT state.

+ *

This is useful when ClientConnector is restarted: an existing connection + * may still be using a network address (same host and same port) that is also + * chosen for a new connection.

+ * + * @param reuseAddress whether rebinding is allowed with sockets in tear-down states + * @see #setReusePort(boolean) + */ public void setReuseAddress(boolean reuseAddress) { this.reuseAddress = reuseAddress; } + /** + * @return whether binding to same host and port is allowed + */ + @ManagedAttribute("Whether binding to same host and port is allowed") + public boolean isReusePort() + { + return reusePort; + } + + /** + *

Sets whether it is allowed to bind multiple sockets to the same + * socket address (same host and same port).

+ * + * @param reusePort whether binding to same host and port is allowed + */ + public void setReusePort(boolean reusePort) + { + this.reusePort = reusePort; + } + + /** + * @return the receive buffer size in bytes, or -1 for the default value + */ + @ManagedAttribute("The receive buffer size in bytes") + public int getReceiveBufferSize() + { + return receiveBufferSize; + } + + public void setReceiveBufferSize(int receiveBufferSize) + { + this.receiveBufferSize = receiveBufferSize; + } + + /** + * @return the send buffer size in bytes, or -1 for the default value + */ + @ManagedAttribute("The send buffer size in bytes") + public int getSendBufferSize() + { + return sendBufferSize; + } + + public void setSendBufferSize(int sendBufferSize) + { + this.sendBufferSize = sendBufferSize; + } + @Override protected void doStart() throws Exception { @@ -246,10 +395,12 @@ public void connect(SocketAddress address, Map context) SocketChannelWithAddress channelWithAddress = factory.newSocketChannelWithAddress(address, context); channel = channelWithAddress.getSocketChannel(); address = channelWithAddress.getSocketAddress(); + + configure(channel); + SocketAddress bindAddress = getBindAddress(); if (bindAddress != null) bind(channel, bindAddress); - configure(channel); boolean connected = true; boolean blocking = isConnectBlocking() && address instanceof InetSocketAddress; @@ -306,33 +457,36 @@ public void accept(SocketChannel channel, Map context) } } - private void bind(SocketChannel channel, SocketAddress bindAddress) + private void bind(SocketChannel channel, SocketAddress bindAddress) throws IOException { - try - { - boolean reuseAddress = getReuseAddress(); - if (LOG.isDebugEnabled()) - LOG.debug("Binding to {} reusing address {}", bindAddress, reuseAddress); - channel.setOption(StandardSocketOptions.SO_REUSEADDR, reuseAddress); - channel.bind(bindAddress); - } - catch (Throwable x) - { - if (LOG.isDebugEnabled()) - LOG.debug("Could not bind {}", channel); - } + if (LOG.isDebugEnabled()) + LOG.debug("Binding {} to {}", channel, bindAddress); + channel.bind(bindAddress); } protected void configure(SocketChannel channel) throws IOException + { + setSocketOption(channel, StandardSocketOptions.TCP_NODELAY, isTCPNoDelay()); + setSocketOption(channel, StandardSocketOptions.SO_REUSEADDR, getReuseAddress()); + setSocketOption(channel, StandardSocketOptions.SO_REUSEPORT, isReusePort()); + int receiveBufferSize = getReceiveBufferSize(); + if (receiveBufferSize >= 0) + setSocketOption(channel, StandardSocketOptions.SO_RCVBUF, receiveBufferSize); + int sendBufferSize = getSendBufferSize(); + if (sendBufferSize >= 0) + setSocketOption(channel, StandardSocketOptions.SO_SNDBUF, sendBufferSize); + } + + private void setSocketOption(SocketChannel channel, SocketOption option, T value) { try { - channel.setOption(StandardSocketOptions.TCP_NODELAY, true); + channel.setOption(option, value); } catch (Throwable x) { if (LOG.isDebugEnabled()) - LOG.debug("Could not configure {}", channel); + LOG.debug("Could not configure {} to {} on {}", option, value, channel); } } diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml index b827eac5e23e..2fed3ded2417 100644 --- a/jetty-server/src/main/config/etc/jetty-http.xml +++ b/jetty-server/src/main/config/etc/jetty-http.xml @@ -39,6 +39,7 @@ + diff --git a/jetty-server/src/main/config/etc/jetty-ssl.xml b/jetty-server/src/main/config/etc/jetty-ssl.xml index 183445c4fb73..f58ee8c35730 100644 --- a/jetty-server/src/main/config/etc/jetty-ssl.xml +++ b/jetty-server/src/main/config/etc/jetty-ssl.xml @@ -32,6 +32,7 @@ + diff --git a/jetty-server/src/main/config/modules/http.mod b/jetty-server/src/main/config/modules/http.mod index 1e1b02a85877..cc5c796df347 100644 --- a/jetty-server/src/main/config/modules/http.mod +++ b/jetty-server/src/main/config/modules/http.mod @@ -40,6 +40,9 @@ etc/jetty-http.xml ## Whether to enable the SO_REUSEADDR socket option. # jetty.http.reuseAddress=true +## Whether to enable the SO_REUSEPORT socket option. +# jetty.http.reusePort=false + ## Whether to enable the TCP_NODELAY socket option on accepted sockets. # jetty.http.acceptedTcpNoDelay=true diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod index d9ed73a74d3c..83d68f931b95 100644 --- a/jetty-server/src/main/config/modules/ssl.mod +++ b/jetty-server/src/main/config/modules/ssl.mod @@ -42,6 +42,9 @@ etc/jetty-ssl-context.xml ## Whether to enable the SO_REUSEADDR socket option. # jetty.ssl.reuseAddress=true +## Whether to enable the SO_REUSEPORT socket option. +# jetty.ssl.reusePort=false + ## Whether to enable the TCP_NODELAY socket option on accepted sockets. # jetty.ssl.acceptedTcpNoDelay=true diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java index a8aaf7e15f86..25592a2260d0 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ServerConnector.java @@ -19,6 +19,7 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; +import java.net.StandardSocketOptions; import java.nio.channels.Channel; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; @@ -77,6 +78,7 @@ public class ServerConnector extends AbstractNetworkConnector private volatile int _localPort = -1; private volatile int _acceptQueueSize = 0; private volatile boolean _reuseAddress = true; + private volatile boolean _reusePort = false; private volatile boolean _acceptedTcpNoDelay = true; private volatile int _acceptedReceiveBufferSize = -1; private volatile int _acceptedSendBufferSize = -1; @@ -332,8 +334,9 @@ protected ServerSocketChannel openAcceptChannel() throws IOException serverChannel = ServerSocketChannel.open(); try { - serverChannel.socket().setReuseAddress(getReuseAddress()); - serverChannel.socket().bind(bindAddress, getAcceptQueueSize()); + serverChannel.setOption(StandardSocketOptions.SO_REUSEADDR, getReuseAddress()); + serverChannel.setOption(StandardSocketOptions.SO_REUSEPORT, isReusePort()); + serverChannel.bind(bindAddress, getAcceptQueueSize()); } catch (Throwable e) { @@ -450,7 +453,7 @@ public void setAcceptQueueSize(int acceptQueueSize) } /** - * @return whether the server socket reuses addresses + * @return whether rebinding the server socket is allowed with sockets in tear-down states * @see ServerSocket#getReuseAddress() */ @ManagedAttribute("Server Socket SO_REUSEADDR") @@ -460,7 +463,7 @@ public boolean getReuseAddress() } /** - * @param reuseAddress whether the server socket reuses addresses + * @param reuseAddress whether rebinding the server socket is allowed with sockets in tear-down states * @see ServerSocket#setReuseAddress(boolean) */ public void setReuseAddress(boolean reuseAddress) @@ -468,6 +471,23 @@ public void setReuseAddress(boolean reuseAddress) _reuseAddress = reuseAddress; } + /** + * @return whether it is allowed to bind multiple server sockets to the same host and port + */ + @ManagedAttribute("Server Socket SO_REUSEPORT") + public boolean isReusePort() + { + return _reusePort; + } + + /** + * @param reusePort whether it is allowed to bind multiple server sockets to the same host and port + */ + public void setReusePort(boolean reusePort) + { + _reusePort = reusePort; + } + /** * @return whether the accepted socket gets {@link java.net.SocketOptions#TCP_NODELAY TCP_NODELAY} enabled. * @see Socket#getTcpNoDelay() diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java index 062fb1645b75..cd5db4be13e6 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java @@ -25,6 +25,7 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.util.Collection; import java.util.concurrent.atomic.AtomicLong; @@ -32,6 +33,9 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.eclipse.jetty.http.HttpHeader; +import org.eclipse.jetty.http.HttpStatus; +import org.eclipse.jetty.http.HttpTester; import org.eclipse.jetty.io.EndPoint; import org.eclipse.jetty.io.SocketChannelEndPoint; import org.eclipse.jetty.logging.StacklessLogging; @@ -50,6 +54,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -208,6 +213,59 @@ public void testReuseAddressFalse() throws Exception } } + @Test + public void testReusePort() throws Exception + { + int port; + try (ServerSocket server = new ServerSocket()) + { + server.setReuseAddress(true); + server.bind(new InetSocketAddress("localhost", 0)); + port = server.getLocalPort(); + } + + Server server = new Server(); + try + { + // Two connectors listening on the same port. + ServerConnector connector1 = new ServerConnector(server, 1, 1); + connector1.setReuseAddress(true); + connector1.setReusePort(true); + connector1.setPort(port); + server.addConnector(connector1); + ServerConnector connector2 = new ServerConnector(server, 1, 1); + connector2.setReuseAddress(true); + connector2.setReusePort(true); + connector2.setPort(port); + server.addConnector(connector2); + + server.setHandler(new AbstractHandler() + { + @Override + public void handle(String target, Request jettyRequest, HttpServletRequest request, HttpServletResponse response) + { + jettyRequest.setHandled(true); + } + }); + + server.start(); + + try (SocketChannel client = SocketChannel.open(new InetSocketAddress("localhost", port))) + { + HttpTester.Request request = HttpTester.newRequest(); + request.put(HttpHeader.HOST, "localhost"); + client.write(request.generate()); + HttpTester.Response response = HttpTester.parseResponse(HttpTester.from(client)); + assertNotNull(response); + assertEquals(HttpStatus.OK_200, response.getStatus()); + } + } + finally + { + server.stop(); + } + } + @Test public void testAddFirstConnectionFactory() { From 2f805826153be1445cecc90f994c861c9e2501cf Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 17 Aug 2021 14:05:16 +1000 Subject: [PATCH 22/24] Fix flaky test from #6562 (#6627) Fix flaky test from #6562 Signed-off-by: Greg Wilkins --- .../org/eclipse/jetty/server/HttpOutputTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java index af67693b8e5c..8e3c7e18a318 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/HttpOutputTest.java @@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import javax.servlet.AsyncContext; import javax.servlet.ServletException; @@ -818,7 +817,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques @Test public void testEmptyBuffer() throws Exception { - AtomicBoolean committed = new AtomicBoolean(); + FuturePromise committed = new FuturePromise<>(); AbstractHandler handler = new AbstractHandler() { @Override @@ -827,7 +826,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques baseRequest.setHandled(true); response.setStatus(200); ((HttpOutput)response.getOutputStream()).write(ByteBuffer.wrap(new byte[0])); - committed.set(response.isCommitted()); + committed.succeeded(response.isCommitted()); } }; @@ -835,13 +834,13 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques handler.start(); String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); - assertThat(committed.get(), is(false)); + assertThat(committed.get(10, TimeUnit.SECONDS), is(false)); } @Test public void testEmptyBufferKnown() throws Exception { - AtomicBoolean committed = new AtomicBoolean(); + FuturePromise committed = new FuturePromise<>(); AbstractHandler handler = new AbstractHandler() { @Override @@ -851,7 +850,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques response.setStatus(200); response.setContentLength(0); ((HttpOutput)response.getOutputStream()).write(ByteBuffer.wrap(new byte[0])); - committed.set(response.isCommitted()); + committed.succeeded(response.isCommitted()); } }; @@ -860,7 +859,7 @@ public void handle(String target, Request baseRequest, HttpServletRequest reques String response = _connector.getResponse("GET / HTTP/1.0\nHost: localhost:80\n\n"); assertThat(response, containsString("HTTP/1.1 200 OK")); assertThat(response, containsString("Content-Length: 0")); - assertThat(committed.get(), is(true)); + assertThat(committed.get(10, TimeUnit.SECONDS), is(true)); } @Test From a5b1845e60fa580531be151fb09ad0584df01f86 Mon Sep 17 00:00:00 2001 From: Greg Wilkins Date: Tue, 17 Aug 2021 16:15:00 +1000 Subject: [PATCH 23/24] Disable ipv6 test for #6624 (#6625) (#6629) Temp disable of test that is breaking the build. --- .../test/java/org/eclipse/jetty/client/HttpClientTLSTest.java | 3 ++- .../java/org/eclipse/jetty/util/ssl/SslContextFactory.java | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java index ca01c19287fb..1efb74bb83e2 100644 --- a/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java +++ b/jetty-client/src/test/java/org/eclipse/jetty/client/HttpClientTLSTest.java @@ -60,7 +60,6 @@ import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.SslConnectionFactory; -import org.eclipse.jetty.toolchain.test.Net; import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.thread.ExecutorThreadPool; @@ -1029,6 +1028,7 @@ public void testForcedNonDomainSNI() throws Exception .send(); assertEquals(HttpStatus.OK_200, response2.getStatus()); + /* TODO Fix. See #6624 if (Net.isIpv6InterfaceAvailable()) { // Send a request with SNI "[::1]", we should get the certificate at alias=ip. @@ -1038,6 +1038,7 @@ public void testForcedNonDomainSNI() throws Exception assertEquals(HttpStatus.OK_200, response3.getStatus()); } + */ } @Test diff --git a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java index 41f36af02833..8132414e7780 100644 --- a/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java +++ b/jetty-util/src/main/java/org/eclipse/jetty/util/ssl/SslContextFactory.java @@ -2178,6 +2178,7 @@ private static List getSniServerNames(SSLEngine sslEngine, List Date: Tue, 17 Aug 2021 16:58:35 +1000 Subject: [PATCH 24/24] Issue #6586 - remove unnecessary dependencies (#6599) * Issue #6586 Remove unnecessary dependencies Signed-off-by: Jan Bartel --- apache-jsp/pom.xml | 6 ------ jetty-jndi/pom.xml | 9 +++++++-- jetty-webapp/pom.xml | 7 ------- .../java/org/eclipse/jetty/webapp/JaasConfiguration.java | 6 +----- .../eclipse/jetty/webapp/JettyWebXmlConfiguration.java | 2 +- .../java/org/eclipse/jetty/webapp/JmxConfiguration.java | 6 +----- .../java/org/eclipse/jetty/webapp/JndiConfiguration.java | 6 +----- .../java/org/eclipse/jetty/webapp/JspConfiguration.java | 6 +----- 8 files changed, 12 insertions(+), 36 deletions(-) diff --git a/apache-jsp/pom.xml b/apache-jsp/pom.xml index 460dd96a1ccc..7bc846eb24f5 100644 --- a/apache-jsp/pom.xml +++ b/apache-jsp/pom.xml @@ -80,12 +80,6 @@ org.mortbay.jasper apache-jsp
- - org.eclipse.jetty - jetty-annotations - ${project.version} - - org.eclipse.jetty jetty-servlet diff --git a/jetty-jndi/pom.xml b/jetty-jndi/pom.xml index 7caf00bf284e..7bbd856a06b3 100644 --- a/jetty-jndi/pom.xml +++ b/jetty-jndi/pom.xml @@ -60,14 +60,19 @@ org.eclipse.jetty - jetty-webapp + jetty-server + ${project.version} + provided + + + org.eclipse.jetty + jetty-security ${project.version} provided org.eclipse.jetty.orbit javax.mail.glassfish - 1.4.1.v201005082020 provided diff --git a/jetty-webapp/pom.xml b/jetty-webapp/pom.xml index 94a4a4b28437..17d4dc26586e 100644 --- a/jetty-webapp/pom.xml +++ b/jetty-webapp/pom.xml @@ -80,13 +80,6 @@ org.slf4j slf4j-api - - - org.eclipse.jetty - jetty-jmx - ${project.version} - test - org.eclipse.jetty jetty-slf4j-impl diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java index c4ea95f41a89..a635445bae4a 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JaasConfiguration.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.webapp; -import java.util.ServiceLoader; - import org.eclipse.jetty.util.Loader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +22,7 @@ *

This configuration configures the WebAppContext server/system classes to * be able to see the org.eclipse.jetty.jaas package. * This class is defined in the webapp package, as it implements the {@link Configuration} interface, - * which is unknown to the jaas package. However, the corresponding {@link ServiceLoader} - * resource is defined in the jaas package, so that this configuration only be - * loaded if the jetty-jaas jars are on the classpath. + * which is unknown to the jaas package. *

*/ public class JaasConfiguration extends AbstractConfiguration diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java index aba74957f58b..7dccaaea820a 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JettyWebXmlConfiguration.java @@ -24,7 +24,7 @@ /** * JettyWebConfiguration. * - * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty6-web.xml, jetty-web.xml or web-jetty.xml + * Looks for XmlConfiguration files in WEB-INF. Searches in order for the first of jetty8-web.xml, jetty-web.xml or web-jetty.xml */ public class JettyWebXmlConfiguration extends AbstractConfiguration { diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java index 2eebbbebad68..ed9cef8fd595 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JmxConfiguration.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.webapp; -import java.util.ServiceLoader; - import org.eclipse.jetty.util.Loader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +22,7 @@ *

This configuration configures the WebAppContext server/system classes to * be able to see the org.eclipse.jetty.jmx package. This class is defined * in the webapp package, as it implements the {@link Configuration} interface, - * which is unknown to the jmx package. However, the corresponding {@link ServiceLoader} - * resource is defined in the jmx package, so that this configuration only be - * loaded if the jetty-jmx jars are on the classpath. + * which is unknown to the jmx package. *

*/ public class JmxConfiguration extends AbstractConfiguration diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java index 5c446b1b2097..2de34e0c6fff 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JndiConfiguration.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.webapp; -import java.util.ServiceLoader; - import org.eclipse.jetty.util.Loader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +22,7 @@ *

This configuration configures the WebAppContext system/server classes to * be able to see the org.eclipse.jetty.jaas package. * This class is defined in the webapp package, as it implements the {@link Configuration} interface, - * which is unknown to the jndi package. However, the corresponding {@link ServiceLoader} - * resource is defined in the jndi package, so that this configuration only be - * loaded if the jetty-jndi jars are on the classpath. + * which is unknown to the jndi package. *

*/ public class JndiConfiguration extends AbstractConfiguration diff --git a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java index 12333f5bbd1e..dd6d1e3cb6cc 100644 --- a/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java +++ b/jetty-webapp/src/main/java/org/eclipse/jetty/webapp/JspConfiguration.java @@ -13,8 +13,6 @@ package org.eclipse.jetty.webapp; -import java.util.ServiceLoader; - import org.eclipse.jetty.util.Loader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -24,9 +22,7 @@ *

This configuration configures the WebAppContext server/system classes to * be able to see the org.eclipse.jetty.jsp and org.eclipse.jetty.apache packages. * This class is defined in the webapp package, as it implements the {@link Configuration} interface, - * which is unknown to the jsp package. However, the corresponding {@link ServiceLoader} - * resource is defined in the jsp package, so that this configuration only be - * loaded if the jetty-jsp jars are on the classpath. + * which is unknown to the jsp package. *

*/ public class JspConfiguration extends AbstractConfiguration