Skip to content

Commit a5005c8

Browse files
committedMay 15, 2024
8330814: Cleanups for KeepAliveCache tests
Reviewed-by: jpai, dfuchs
1 parent 1a94447 commit a5005c8

File tree

5 files changed

+221
-177
lines changed

5 files changed

+221
-177
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,9 +24,9 @@
2424
/*
2525
* @test
2626
* @bug 5045306 6356004 6993490 8255124
27+
* @summary Http keep-alive implementation is not efficient
2728
* @library /test/lib
2829
* @run main/othervm B5045306
29-
* @summary Http keep-alive implementation is not efficient
3030
*/
3131

3232
import java.io.IOException;
@@ -42,14 +42,17 @@
4242
import java.net.URL;
4343
import java.util.ArrayList;
4444
import java.util.List;
45+
import java.util.concurrent.ExecutorService;
4546
import java.util.concurrent.Executors;
4647

4748
import com.sun.net.httpserver.HttpExchange;
4849
import com.sun.net.httpserver.HttpHandler;
4950
import com.sun.net.httpserver.HttpServer;
5051

52+
import jdk.test.lib.net.URIBuilder;
53+
5154
/* Part 1:
52-
* The http client makes a connection to a URL whos content contains a lot of
55+
* The http client makes a connection to a URL whose content contains a lot of
5356
* data, more than can fit in the socket buffer. The client only reads
5457
* 1 byte of the data from the InputStream leaving behind more data than can
5558
* fit in the socket buffer. The client then makes a second call to the http
@@ -63,150 +66,166 @@
6366

6467
public class B5045306 {
6568
static HttpServer server;
66-
67-
public static void main(String[] args) {
68-
startHttpServer();
69-
clientHttpCalls();
70-
}
69+
static ExecutorService executor = Executors.newSingleThreadExecutor();
7170

7271
public static void startHttpServer() {
7372
try {
74-
server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), 0), 10, "/", new SimpleHttpTransactionHandler());
75-
server.setExecutor(Executors.newSingleThreadExecutor());
76-
server.start();
73+
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 10, "/", new SimpleHttpTransactionHandler());
7774
} catch (IOException e) {
78-
e.printStackTrace();
75+
throw new RuntimeException(e);
7976
}
77+
server.setExecutor(executor);
78+
server.start();
79+
System.out.println("http server listens on: " + server.getAddress());
8080
}
8181

82-
public static void clientHttpCalls() {
82+
public static void stopHttpServer() {
83+
server.stop(1);
84+
executor.shutdown();
85+
}
86+
87+
public static void clientHttpCalls() throws Exception {
8388
List<Throwable> uncaught = new ArrayList<>();
8489
Thread.setDefaultUncaughtExceptionHandler((t, ex) -> {
8590
uncaught.add(ex);
8691
});
87-
try {
88-
System.out.println("http server listen on: " + server.getAddress().getPort());
89-
String hostAddr = InetAddress.getLocalHost().getHostAddress();
90-
if (hostAddr.indexOf(':') > -1) hostAddr = "[" + hostAddr + "]";
91-
String baseURLStr = "http://" + hostAddr + ":" + server.getAddress().getPort() + "/";
9292

93-
URL bigDataURL = new URL (baseURLStr + "firstCall");
94-
URL smallDataURL = new URL (baseURLStr + "secondCall");
93+
URL bigDataURL = URIBuilder.newBuilder()
94+
.scheme("http")
95+
.loopback()
96+
.port(server.getAddress().getPort())
97+
.path("/firstCall")
98+
.toURL();
99+
100+
URL smallDataURL = URIBuilder.newBuilder()
101+
.scheme("http")
102+
.loopback()
103+
.port(server.getAddress().getPort())
104+
.path("/secondCall")
105+
.toURL();
95106

96-
HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection(Proxy.NO_PROXY);
107+
HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection(Proxy.NO_PROXY);
97108

98-
//Only read 1 byte of response data and close the stream
99-
InputStream is = uc.getInputStream();
109+
// Only read 1 byte of response data and close the stream
110+
try (InputStream is = uc.getInputStream()) {
100111
byte[] ba = new byte[1];
101112
is.read(ba);
102-
is.close();
103-
104-
// Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
105-
try { Thread.sleep(2000); } catch (Exception e) {}
106-
107-
uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
108-
uc.getResponseCode();
109-
110-
if (SimpleHttpTransactionHandler.failed)
111-
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
112-
113-
// Part 2
114-
URL part2Url = new URL (baseURLStr + "part2");
115-
uc = (HttpURLConnection)part2Url.openConnection(Proxy.NO_PROXY);
116-
is = uc.getInputStream();
117-
is.close();
118-
119-
// Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
120-
try { Thread.sleep(2000); } catch (Exception e) {}
121-
122-
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
123-
if (threadMXBean.isThreadCpuTimeSupported()) {
124-
long[] threads = threadMXBean.getAllThreadIds();
125-
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
126-
for (int i=0; i<threadInfo.length; i++) {
127-
if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
128-
System.out.println("Found Keep-Alive-SocketCleaner thread");
129-
long threadID = threadInfo[i].getThreadId();
130-
long before = threadMXBean.getThreadCpuTime(threadID);
131-
try { Thread.sleep(2000); } catch (Exception e) {}
132-
long after = threadMXBean.getThreadCpuTime(threadID);
133-
134-
if (before ==-1 || after == -1)
135-
break; // thread has died, OK
136-
137-
// if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
138-
// can assume a recursive loop.
139-
long total = after - before;
140-
if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
141-
throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
142-
}
113+
}
114+
115+
// Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
116+
try { Thread.sleep(2000); } catch (Exception e) {}
117+
118+
uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
119+
uc.getResponseCode();
120+
121+
if (SimpleHttpTransactionHandler.failed)
122+
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");
123+
124+
// Part 2
125+
URL part2Url = URIBuilder.newBuilder()
126+
.scheme("http")
127+
.loopback()
128+
.port(server.getAddress().getPort())
129+
.path("/part2")
130+
.toURL();
131+
132+
uc = (HttpURLConnection)part2Url.openConnection(Proxy.NO_PROXY);
133+
try (InputStream is = uc.getInputStream()) {}
134+
135+
// Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
136+
try { Thread.sleep(2000); } catch (Exception e) {}
137+
138+
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
139+
if (threadMXBean.isThreadCpuTimeSupported()) {
140+
long[] threads = threadMXBean.getAllThreadIds();
141+
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
142+
for (int i = 0; i < threadInfo.length; i++) {
143+
if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
144+
System.out.println("Found Keep-Alive-SocketCleaner thread");
145+
long threadID = threadInfo[i].getThreadId();
146+
long before = threadMXBean.getThreadCpuTime(threadID);
147+
try { Thread.sleep(2000); } catch (Exception e) {}
148+
long after = threadMXBean.getThreadCpuTime(threadID);
149+
150+
if (before ==-1 || after == -1)
151+
break; // thread has died, OK
152+
153+
// if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
154+
// can assume a recursive loop.
155+
long total = after - before;
156+
if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
157+
throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
143158
}
144159
}
145-
146-
} catch (IOException e) {
147-
e.printStackTrace();
148-
} finally {
149-
server.stop(1);
150160
}
151161
if (!uncaught.isEmpty()) {
152162
throw new RuntimeException("Unhandled exception:", uncaught.get(0));
153163
}
154164
}
155-
}
156165

157-
class SimpleHttpTransactionHandler implements HttpHandler
158-
{
159-
static volatile boolean failed = false;
166+
static class SimpleHttpTransactionHandler implements HttpHandler {
167+
static volatile boolean failed = false;
160168

161-
// Need to have enough data here that is too large for the socket buffer to hold.
162-
// Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
163-
static final int RESPONSE_DATA_LENGTH = 128 * 1024;
169+
// Need to have enough data here that is too large for the socket buffer to hold.
170+
// Also http.KeepAlive.remainingData must be greater than this value, default is 256K.
171+
static final int RESPONSE_DATA_LENGTH = 128 * 1024;
164172

165-
int port1;
173+
int port1;
166174

167-
public void handle(HttpExchange trans) {
168-
try {
169-
String path = trans.getRequestURI().getPath();
170-
if (path.equals("/firstCall")) {
171-
port1 = trans.getRemoteAddress().getPort();
172-
System.out.println("First connection on client port = " + port1);
173-
174-
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
175-
for (int i=0; i<responseBody.length; i++)
176-
responseBody[i] = 0x41;
177-
trans.sendResponseHeaders(200, responseBody.length);
178-
try (OutputStream os = trans.getResponseBody()) {
175+
public void handle(HttpExchange trans) {
176+
try {
177+
String path = trans.getRequestURI().getPath();
178+
if (path.equals("/firstCall")) {
179+
port1 = trans.getRemoteAddress().getPort();
180+
System.out.println("First connection on client port = " + port1);
181+
182+
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
183+
for (int i=0; i<responseBody.length; i++)
184+
responseBody[i] = 0x41;
185+
trans.sendResponseHeaders(200, responseBody.length);
186+
try (OutputStream os = trans.getResponseBody()) {
187+
os.write(responseBody);
188+
}
189+
} else if (path.equals("/secondCall")) {
190+
int port2 = trans.getRemoteAddress().getPort();
191+
System.out.println("Second connection on client port = " + port2);
192+
193+
if (port1 != port2)
194+
failed = true;
195+
196+
/* Force the server to not respond for more that the timeout
197+
* set by the keepalive cleaner (5000 millis). This ensures the
198+
* timeout is correctly resets the default read timeout,
199+
* infinity. See 6993490. */
200+
System.out.println("server sleeping...");
201+
try {Thread.sleep(6000); } catch (InterruptedException e) {}
202+
trans.sendResponseHeaders(200, -1);
203+
} else if (path.equals("/part2")) {
204+
System.out.println("Call to /part2");
205+
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
206+
for (int i=0; i<responseBody.length; i++)
207+
responseBody[i] = 0x41;
208+
// override the Content-length header to be greater than the actual response body
209+
trans.sendResponseHeaders(200, responseBody.length+1);
210+
OutputStream os = trans.getResponseBody();
179211
os.write(responseBody);
212+
// now close the socket
213+
// closing the stream here would throw; close the exchange instead
214+
trans.close();
180215
}
181-
} else if (path.equals("/secondCall")) {
182-
int port2 = trans.getRemoteAddress().getPort();
183-
System.out.println("Second connection on client port = " + port2);
184-
185-
if (port1 != port2)
186-
failed = true;
187-
188-
/* Force the server to not respond for more that the timeout
189-
* set by the keepalive cleaner (5000 millis). This ensures the
190-
* timeout is correctly resets the default read timeout,
191-
* infinity. See 6993490. */
192-
System.out.println("server sleeping...");
193-
try {Thread.sleep(6000); } catch (InterruptedException e) {}
194-
trans.sendResponseHeaders(200, -1);
195-
} else if(path.equals("/part2")) {
196-
System.out.println("Call to /part2");
197-
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
198-
for (int i=0; i<responseBody.length; i++)
199-
responseBody[i] = 0x41;
200-
// override the Content-length header to be greater than the actual response body
201-
trans.sendResponseHeaders(200, responseBody.length+1);
202-
OutputStream os = trans.getResponseBody();
203-
os.write(responseBody);
204-
// now close the socket
205-
// closing the stream here would throw; close the exchange instead
206-
trans.close();
216+
} catch (Exception e) {
217+
e.printStackTrace();
218+
failed = true;
207219
}
208-
} catch (Exception e) {
209-
e.printStackTrace();
220+
}
221+
}
222+
223+
public static void main(String[] args) throws Exception {
224+
startHttpServer();
225+
try {
226+
clientHttpCalls();
227+
} finally {
228+
stopHttpServer();
210229
}
211230
}
212231
}

0 commit comments

Comments
 (0)