Skip to content

Commit 5fd904c

Browse files
authored
test:refactor: AbstractSimultaneousConnectionsTest uses Vert.x as backend server
Signed-off-by: Marc Nuri <marc@marcnuri.com>
1 parent c96ad2f commit 5fd904c

File tree

8 files changed

+93
-70
lines changed

8 files changed

+93
-70
lines changed

httpclient-jdk/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@
7979
<artifactId>assertj-core</artifactId>
8080
<scope>test</scope>
8181
</dependency>
82+
<dependency>
83+
<groupId>org.awaitility</groupId>
84+
<artifactId>awaitility</artifactId>
85+
<scope>test</scope>
86+
</dependency>
8287
</dependencies>
8388

8489
<build>

httpclient-jdk/src/test/java/io/fabric8/kubernetes/client/jdkhttp/JdkHttpClientSimultaneousConnectionsTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,12 @@ public class JdkHttpClientSimultaneousConnectionsTest extends AbstractSimultaneo
2424
protected HttpClient.Factory getHttpClientFactory() {
2525
return new JdkHttpClientFactory();
2626
}
27+
28+
@Override
29+
public void http1Connections() {
30+
// NO-OP
31+
// This test will only pass when it's run in isolation, it seems that the JDK HttpClient eventually uses a shared thread
32+
// pool that reaches a limit and this test will effectively block any further processing after a few connections are open.
33+
// - jdk.internal.net.http.HttpClientImpl.ASYNC_POOL
34+
}
2735
}

httpclient-jetty/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
<groupId>org.assertj</groupId>
9494
<artifactId>assertj-core</artifactId>
9595
</dependency>
96+
<dependency>
97+
<groupId>org.awaitility</groupId>
98+
<artifactId>awaitility</artifactId>
99+
<scope>test</scope>
100+
</dependency>
96101
</dependencies>
97102

98103
<build>

httpclient-okhttp/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
<artifactId>assertj-core</artifactId>
9494
<scope>test</scope>
9595
</dependency>
96+
<dependency>
97+
<groupId>org.awaitility</groupId>
98+
<artifactId>awaitility</artifactId>
99+
<scope>test</scope>
100+
</dependency>
96101
</dependencies>
97102

98103
<build>

httpclient-vertx/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@
9191
<artifactId>assertj-core</artifactId>
9292
<scope>test</scope>
9393
</dependency>
94+
<dependency>
95+
<groupId>org.awaitility</groupId>
96+
<artifactId>awaitility</artifactId>
97+
<scope>test</scope>
98+
</dependency>
9499
<dependency>
95100
<!-- Required by SslTest -->
96101
<groupId>org.bouncycastle</groupId>

junit/mockwebserver/src/main/java/io/fabric8/mockwebserver/MockWebServer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ public synchronized void shutdown() {
168168
httpClose.onComplete(onComplete);
169169
await(httpClose, "Unable to close MockWebServer");
170170
}
171+
await(vertx.close(), "Unable to close Vertx");
171172
}
172173

173174
@Override

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractAsyncBodyTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,6 @@ public void consumeBytesProcessesLargeBodies() throws Exception {
114114
asyncBodyResponse.body().consume();
115115
asyncBodyResponse.body().done().get(10L, TimeUnit.SECONDS);
116116
assertThat(responseText.toString()).isEqualTo(largeBody);
117-
118117
}
119118
}
120119

kubernetes-client-api/src/test/java/io/fabric8/kubernetes/client/http/AbstractSimultaneousConnectionsTest.java

Lines changed: 64 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -15,25 +15,28 @@
1515
*/
1616
package io.fabric8.kubernetes.client.http;
1717

18-
import com.sun.net.httpserver.HttpExchange;
19-
import com.sun.net.httpserver.HttpHandler;
20-
import com.sun.net.httpserver.HttpServer;
18+
import io.fabric8.kubernetes.client.RequestConfigBuilder;
2119
import io.fabric8.mockwebserver.MockWebServer;
2220
import io.fabric8.mockwebserver.MockWebServerListener;
2321
import io.fabric8.mockwebserver.http.MockResponse;
2422
import io.fabric8.mockwebserver.http.RecordedHttpConnection;
2523
import io.fabric8.mockwebserver.http.Response;
2624
import io.fabric8.mockwebserver.http.WebSocketListener;
2725
import io.fabric8.mockwebserver.vertx.Protocol;
26+
import io.vertx.core.Vertx;
27+
import io.vertx.core.http.HttpServer;
28+
import io.vertx.core.http.HttpServerOptions;
29+
import io.vertx.core.http.HttpServerRequest;
30+
import io.vertx.core.http.HttpVersion;
31+
import io.vertx.core.net.NetServerOptions;
32+
import org.awaitility.Awaitility;
2833
import org.junit.jupiter.api.AfterEach;
2934
import org.junit.jupiter.api.BeforeEach;
3035
import org.junit.jupiter.api.DisplayName;
3136
import org.junit.jupiter.api.Test;
3237
import org.junit.jupiter.api.condition.DisabledOnOs;
3338
import org.junit.jupiter.api.condition.OS;
3439

35-
import java.io.IOException;
36-
import java.net.InetSocketAddress;
3740
import java.net.URI;
3841
import java.util.Collection;
3942
import java.util.Collections;
@@ -42,8 +45,6 @@
4245
import java.util.concurrent.ConcurrentHashMap;
4346
import java.util.concurrent.CountDownLatch;
4447
import java.util.concurrent.CyclicBarrier;
45-
import java.util.concurrent.ExecutorService;
46-
import java.util.concurrent.Executors;
4748
import java.util.concurrent.TimeUnit;
4849
import java.util.stream.IntStream;
4950

@@ -59,29 +60,24 @@ public abstract class AbstractSimultaneousConnectionsTest {
5960

6061
private RegisteredConnections registeredConnections;
6162
private MockWebServer mockWebServer;
62-
private ExecutorService httpExecutor;
63-
private HttpServer httpServer;
63+
private Vertx vertx;
6464

6565
private HttpClient.Builder clientBuilder;
6666

6767
@BeforeEach
68-
void prepareServerAndBuilder() throws IOException {
68+
void prepareServerAndBuilder() {
6969
registeredConnections = new RegisteredConnections();
7070
mockWebServer = new MockWebServer();
7171
mockWebServer.addListener(registeredConnections);
72-
httpExecutor = Executors.newCachedThreadPool();
73-
httpServer = HttpServer.create(new InetSocketAddress(0), 0);
74-
httpServer.setExecutor(httpExecutor);
75-
httpServer.start();
72+
vertx = Vertx.vertx();
7673
clientBuilder = getHttpClientFactory().newBuilder()
7774
.connectTimeout(60, TimeUnit.SECONDS);
7875
}
7976

8077
@AfterEach
8178
void stopServer() {
8279
mockWebServer.shutdown();
83-
httpServer.stop(0);
84-
httpExecutor.shutdownNow();
80+
vertx.close();
8581
}
8682

8783
protected abstract HttpClient.Factory getHttpClientFactory();
@@ -95,20 +91,21 @@ private void withHttp1() {
9591
@DisplayName("Should be able to make 2048 simultaneous HTTP/1.x connections before processing the response")
9692
@DisabledOnOs(OS.WINDOWS)
9793
public void http1Connections() throws Exception {
98-
final DelayedResponseHandler handler = new DelayedResponseHandler(MAX_HTTP_1_CONNECTIONS,
99-
exchange -> {
100-
exchange.sendResponseHeaders(204, -1);
101-
exchange.close();
102-
});
103-
httpServer.createContext("/http", handler);
104-
try (final HttpClient client = clientBuilder.build()) {
105-
final Collection<CompletableFuture<HttpResponse<AsyncBody>>> asyncResponses = ConcurrentHashMap.newKeySet();
106-
final HttpRequest request = client.newHttpRequestBuilder()
107-
.uri(String.format("http://localhost:%s/http", httpServer.getAddress().getPort()))
108-
.build();
94+
final Collection<CompletableFuture<HttpResponse<AsyncBody>>> asyncResponses = ConcurrentHashMap.newKeySet();
95+
try (
96+
var server = new DelayedResponseHttp1Server(vertx, MAX_HTTP_1_CONNECTIONS);
97+
var client = clientBuilder.tag(new RequestConfigBuilder().withRequestRetryBackoffLimit(0).build()).build()) {
10998
for (int it = 0; it < MAX_HTTP_1_CONNECTIONS; it++) {
99+
final HttpRequest request = client.newHttpRequestBuilder()
100+
.uri(server.uri() + "?" + it)
101+
.build();
110102
asyncResponses.add(client.consumeBytes(request, (value, asyncBody) -> asyncBody.consume()));
111-
handler.await();
103+
}
104+
server.await();
105+
assertThat(server.requests)
106+
.hasSize(MAX_HTTP_1_CONNECTIONS);
107+
for (HttpServerRequest serverRequest : server.requests) {
108+
serverRequest.response().setStatusCode(204).end();
112109
}
113110
CompletableFuture.allOf(asyncResponses.toArray(new CompletableFuture[0])).get(70, TimeUnit.SECONDS);
114111
assertThat(asyncResponses)
@@ -126,19 +123,18 @@ public void http1Connections() throws Exception {
126123
@DisplayName("Should be able to make 1024 simultaneous HTTP connections before upgrading to WebSocket")
127124
@DisabledOnOs(OS.WINDOWS)
128125
public void http1WebSocketConnectionsBeforeUpgrade() throws Exception {
129-
final DelayedResponseHandler handler = new DelayedResponseHandler(MAX_HTTP_1_WS_CONNECTIONS,
130-
exchange -> exchange.sendResponseHeaders(404, -1));
131-
httpServer.createContext("/http", handler);
132-
try (final HttpClient client = clientBuilder.build()) {
126+
try (var server = new DelayedResponseHttp1Server(vertx, MAX_HTTP_1_WS_CONNECTIONS); var client = clientBuilder.build()) {
133127
for (int it = 0; it < MAX_HTTP_1_WS_CONNECTIONS; it++) {
134128
client.newWebSocketBuilder()
135-
.uri(URI.create(String.format("http://localhost:%s/http", httpServer.getAddress().getPort())))
129+
.uri(URI.create(server.uri()))
136130
.buildAsync(new WebSocket.Listener() {
137131
});
138-
handler.await();
139132
}
133+
server.await();
134+
assertThat(server.requests)
135+
.hasSize(MAX_HTTP_1_WS_CONNECTIONS);
136+
server.requests.forEach(request -> request.response().setStatusCode(101).end());
140137
}
141-
assertThat(handler.connectionCount.get(60, TimeUnit.SECONDS)).isEqualTo(MAX_HTTP_1_WS_CONNECTIONS);
142138
}
143139

144140
@Test
@@ -192,47 +188,46 @@ public void onMessage(WebSocket webSocket, String text) {
192188
}
193189
}
194190

195-
private static class DelayedResponseHandler implements HttpHandler {
196-
197-
private final int requestCount;
198-
private final CyclicBarrier barrier;
199-
private final Set<HttpExchange> exchanges;
200-
private final CompletableFuture<Integer> connectionCount;
201-
private final ExecutorService executorService;
202-
203-
private DelayedResponseHandler(int requestCount, HttpHandler handler) {
204-
this.requestCount = requestCount;
205-
this.barrier = new CyclicBarrier(2);
206-
exchanges = ConcurrentHashMap.newKeySet();
207-
connectionCount = new CompletableFuture<>();
208-
executorService = Executors.newFixedThreadPool(1);
209-
connectionCount.thenRunAsync(() -> {
210-
for (HttpExchange exchange : exchanges) {
211-
try {
212-
handler.handle(exchange);
213-
} catch (IOException ignore) {
214-
// NO OP
215-
}
216-
}
217-
}, executorService)
218-
.whenComplete((unused, throwable) -> executorService.shutdownNow());
191+
private static class DelayedResponseHttp1Server implements AutoCloseable {
192+
193+
private final int connections;
194+
private final HttpServer httpServer;
195+
private final Collection<HttpServerRequest> requests;
196+
private final CountDownLatch connectionLatch;
197+
198+
private DelayedResponseHttp1Server(Vertx vertx, int connections) throws Exception {
199+
this.connections = connections;
200+
requests = ConcurrentHashMap.newKeySet();
201+
connectionLatch = new CountDownLatch(connections);
202+
httpServer = vertx.createHttpServer(new HttpServerOptions()
203+
.setPort(NetServerOptions.DEFAULT_PORT)
204+
.setAlpnVersions(Collections.singletonList(HttpVersion.HTTP_1_1)));
205+
httpServer.connectionHandler(event -> connectionLatch.countDown());
206+
httpServer.requestHandler(requests::add);
207+
httpServer.listen().toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS);
219208
}
220209

221210
@Override
222-
public void handle(HttpExchange exchange) {
223-
exchanges.add(exchange);
224-
await();
225-
if (exchanges.size() == requestCount) {
226-
connectionCount.complete(requestCount);
227-
}
211+
public void close() throws Exception {
212+
requests.forEach(request -> request.connection().close());
213+
requests.clear();
214+
httpServer.close().toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS);
215+
}
228216

217+
private String uri() {
218+
return String.format("http://localhost:%s/http-1-connections", httpServer.actualPort());
229219
}
230220

231-
public final void await() {
221+
private void await() {
232222
try {
233-
barrier.await(5, TimeUnit.SECONDS);
234-
} catch (Exception ex) {
235-
throw new RuntimeException("Failed to await the barrier");
223+
if (!connectionLatch.await(10, TimeUnit.SECONDS)) {
224+
throw new AssertionError(
225+
"Failed to await the connection latch, remaining connections to open: " + connectionLatch.getCount());
226+
}
227+
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> requests.size() == connections);
228+
} catch (InterruptedException e) {
229+
Thread.currentThread().interrupt();
230+
throw new RuntimeException("Failed to await the connection latch (interrupted)", e);
236231
}
237232
}
238233
}

0 commit comments

Comments
 (0)