Skip to content

Commit

Permalink
8330814: Cleanups for KeepAliveCache tests
Browse files Browse the repository at this point in the history
Backport-of: 1e173f9d17e4a42e758217ac98aec4356acac0fa
  • Loading branch information
RealCLanger committed Jun 13, 2024
1 parent 1831a95 commit a1a56cf
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 177 deletions.
253 changes: 136 additions & 117 deletions test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand All @@ -24,9 +24,9 @@
/*
* @test
* @bug 5045306 6356004 6993490 8255124
* @summary Http keep-alive implementation is not efficient
* @library /test/lib
* @run main/othervm B5045306
* @summary Http keep-alive implementation is not efficient
*/

import java.io.IOException;
Expand All @@ -42,14 +42,17 @@
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import jdk.test.lib.net.URIBuilder;

/* Part 1:
* The http client makes a connection to a URL whos content contains a lot of
* The http client makes a connection to a URL whose content contains a lot of
* data, more than can fit in the socket buffer. The client only reads
* 1 byte of the data from the InputStream leaving behind more data than can
* fit in the socket buffer. The client then makes a second call to the http
Expand All @@ -63,150 +66,166 @@

public class B5045306 {
static HttpServer server;

public static void main(String[] args) {
startHttpServer();
clientHttpCalls();
}
static ExecutorService executor = Executors.newSingleThreadExecutor();

public static void startHttpServer() {
try {
server = HttpServer.create(new InetSocketAddress(InetAddress.getLocalHost(), 0), 10, "/", new SimpleHttpTransactionHandler());
server.setExecutor(Executors.newSingleThreadExecutor());
server.start();
server = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 10, "/", new SimpleHttpTransactionHandler());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
server.setExecutor(executor);
server.start();
System.out.println("http server listens on: " + server.getAddress());
}

public static void clientHttpCalls() {
public static void stopHttpServer() {
server.stop(1);
executor.shutdown();
}

public static void clientHttpCalls() throws Exception {
List<Throwable> uncaught = new ArrayList<>();
Thread.setDefaultUncaughtExceptionHandler((t, ex) -> {
uncaught.add(ex);
});
try {
System.out.println("http server listen on: " + server.getAddress().getPort());
String hostAddr = InetAddress.getLocalHost().getHostAddress();
if (hostAddr.indexOf(':') > -1) hostAddr = "[" + hostAddr + "]";
String baseURLStr = "http://" + hostAddr + ":" + server.getAddress().getPort() + "/";

URL bigDataURL = new URL (baseURLStr + "firstCall");
URL smallDataURL = new URL (baseURLStr + "secondCall");
URL bigDataURL = URIBuilder.newBuilder()
.scheme("http")
.loopback()
.port(server.getAddress().getPort())
.path("/firstCall")
.toURL();

URL smallDataURL = URIBuilder.newBuilder()
.scheme("http")
.loopback()
.port(server.getAddress().getPort())
.path("/secondCall")
.toURL();

HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection(Proxy.NO_PROXY);
HttpURLConnection uc = (HttpURLConnection)bigDataURL.openConnection(Proxy.NO_PROXY);

//Only read 1 byte of response data and close the stream
InputStream is = uc.getInputStream();
// Only read 1 byte of response data and close the stream
try (InputStream is = uc.getInputStream()) {
byte[] ba = new byte[1];
is.read(ba);
is.close();

// Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
try { Thread.sleep(2000); } catch (Exception e) {}

uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
uc.getResponseCode();

if (SimpleHttpTransactionHandler.failed)
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");

// Part 2
URL part2Url = new URL (baseURLStr + "part2");
uc = (HttpURLConnection)part2Url.openConnection(Proxy.NO_PROXY);
is = uc.getInputStream();
is.close();

// Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
try { Thread.sleep(2000); } catch (Exception e) {}

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (threadMXBean.isThreadCpuTimeSupported()) {
long[] threads = threadMXBean.getAllThreadIds();
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
for (int i=0; i<threadInfo.length; i++) {
if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
System.out.println("Found Keep-Alive-SocketCleaner thread");
long threadID = threadInfo[i].getThreadId();
long before = threadMXBean.getThreadCpuTime(threadID);
try { Thread.sleep(2000); } catch (Exception e) {}
long after = threadMXBean.getThreadCpuTime(threadID);

if (before ==-1 || after == -1)
break; // thread has died, OK

// if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
// can assume a recursive loop.
long total = after - before;
if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
}
}

// Allow the KeepAliveStreamCleaner thread to read the data left behind and cache the connection.
try { Thread.sleep(2000); } catch (Exception e) {}

uc = (HttpURLConnection)smallDataURL.openConnection(Proxy.NO_PROXY);
uc.getResponseCode();

if (SimpleHttpTransactionHandler.failed)
throw new RuntimeException("Failed: Initial Keep Alive Connection is not being reused");

// Part 2
URL part2Url = URIBuilder.newBuilder()
.scheme("http")
.loopback()
.port(server.getAddress().getPort())
.path("/part2")
.toURL();

uc = (HttpURLConnection)part2Url.openConnection(Proxy.NO_PROXY);
try (InputStream is = uc.getInputStream()) {}

// Allow the KeepAliveStreamCleaner thread to try and read the data left behind and cache the connection.
try { Thread.sleep(2000); } catch (Exception e) {}

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
if (threadMXBean.isThreadCpuTimeSupported()) {
long[] threads = threadMXBean.getAllThreadIds();
ThreadInfo[] threadInfo = threadMXBean.getThreadInfo(threads);
for (int i = 0; i < threadInfo.length; i++) {
if (threadInfo[i].getThreadName().equals("Keep-Alive-SocketCleaner")) {
System.out.println("Found Keep-Alive-SocketCleaner thread");
long threadID = threadInfo[i].getThreadId();
long before = threadMXBean.getThreadCpuTime(threadID);
try { Thread.sleep(2000); } catch (Exception e) {}
long after = threadMXBean.getThreadCpuTime(threadID);

if (before ==-1 || after == -1)
break; // thread has died, OK

// if Keep-Alive-SocketCleaner consumes more than 50% of cpu then we
// can assume a recursive loop.
long total = after - before;
if (total >= 1000000000) // 1 second, or 1 billion nanoseconds
throw new RuntimeException("Failed: possible recursive loop in Keep-Alive-SocketCleaner");
}
}

} catch (IOException e) {
e.printStackTrace();
} finally {
server.stop(1);
}
if (!uncaught.isEmpty()) {
throw new RuntimeException("Unhandled exception:", uncaught.get(0));
}
}
}

class SimpleHttpTransactionHandler implements HttpHandler
{
static volatile boolean failed = false;
static class SimpleHttpTransactionHandler implements HttpHandler {
static volatile boolean failed = false;

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

int port1;
int port1;

public void handle(HttpExchange trans) {
try {
String path = trans.getRequestURI().getPath();
if (path.equals("/firstCall")) {
port1 = trans.getRemoteAddress().getPort();
System.out.println("First connection on client port = " + port1);

byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
for (int i=0; i<responseBody.length; i++)
responseBody[i] = 0x41;
trans.sendResponseHeaders(200, responseBody.length);
try (OutputStream os = trans.getResponseBody()) {
public void handle(HttpExchange trans) {
try {
String path = trans.getRequestURI().getPath();
if (path.equals("/firstCall")) {
port1 = trans.getRemoteAddress().getPort();
System.out.println("First connection on client port = " + port1);

byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
for (int i=0; i<responseBody.length; i++)
responseBody[i] = 0x41;
trans.sendResponseHeaders(200, responseBody.length);
try (OutputStream os = trans.getResponseBody()) {
os.write(responseBody);
}
} else if (path.equals("/secondCall")) {
int port2 = trans.getRemoteAddress().getPort();
System.out.println("Second connection on client port = " + port2);

if (port1 != port2)
failed = true;

/* Force the server to not respond for more that the timeout
* set by the keepalive cleaner (5000 millis). This ensures the
* timeout is correctly resets the default read timeout,
* infinity. See 6993490. */
System.out.println("server sleeping...");
try {Thread.sleep(6000); } catch (InterruptedException e) {}
trans.sendResponseHeaders(200, -1);
} else if (path.equals("/part2")) {
System.out.println("Call to /part2");
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
for (int i=0; i<responseBody.length; i++)
responseBody[i] = 0x41;
// override the Content-length header to be greater than the actual response body
trans.sendResponseHeaders(200, responseBody.length+1);
OutputStream os = trans.getResponseBody();
os.write(responseBody);
// now close the socket
// closing the stream here would throw; close the exchange instead
trans.close();
}
} else if (path.equals("/secondCall")) {
int port2 = trans.getRemoteAddress().getPort();
System.out.println("Second connection on client port = " + port2);

if (port1 != port2)
failed = true;

/* Force the server to not respond for more that the timeout
* set by the keepalive cleaner (5000 millis). This ensures the
* timeout is correctly resets the default read timeout,
* infinity. See 6993490. */
System.out.println("server sleeping...");
try {Thread.sleep(6000); } catch (InterruptedException e) {}
trans.sendResponseHeaders(200, -1);
} else if(path.equals("/part2")) {
System.out.println("Call to /part2");
byte[] responseBody = new byte[RESPONSE_DATA_LENGTH];
for (int i=0; i<responseBody.length; i++)
responseBody[i] = 0x41;
// override the Content-length header to be greater than the actual response body
trans.sendResponseHeaders(200, responseBody.length+1);
OutputStream os = trans.getResponseBody();
os.write(responseBody);
// now close the socket
// closing the stream here would throw; close the exchange instead
trans.close();
} catch (Exception e) {
e.printStackTrace();
failed = true;
}
} catch (Exception e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws Exception {
startHttpServer();
try {
clientHttpCalls();
} finally {
stopHttpServer();
}
}
}
Loading

3 comments on commit a1a56cf

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RealCLanger
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/backport jdk17u-dev

@openjdk
Copy link

@openjdk openjdk bot commented on a1a56cf Jun 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RealCLanger Could not automatically backport a1a56cf8 to openjdk/jdk17u-dev due to conflicts in the following files:

  • test/jdk/sun/net/www/http/KeepAliveCache/B5045306.java
  • test/jdk/sun/net/www/http/KeepAliveCache/B8293562.java

Please fetch the appropriate branch/commit and manually resolve these conflicts by using the following commands in your personal fork of openjdk/jdk17u-dev. Note: these commands are just some suggestions and you can use other equivalent commands you know.

# Fetch the up-to-date version of the target branch
$ git fetch --no-tags https://git.openjdk.org/jdk17u-dev.git master:master

# Check out the target branch and create your own branch to backport
$ git checkout master
$ git checkout -b backport-RealCLanger-a1a56cf8-master

# Fetch the commit you want to backport
$ git fetch --no-tags https://git.openjdk.org/jdk21u-dev.git a1a56cf88b1d4022441a51aac53f9f7cf47096fb

# Backport the commit
$ git cherry-pick --no-commit a1a56cf88b1d4022441a51aac53f9f7cf47096fb
# Resolve conflicts now

# Commit the files you have modified
$ git add files/with/resolved/conflicts
$ git commit -m 'Backport a1a56cf88b1d4022441a51aac53f9f7cf47096fb'

Once you have resolved the conflicts as explained above continue with creating a pull request towards the openjdk/jdk17u-dev with the title Backport a1a56cf88b1d4022441a51aac53f9f7cf47096fb.

Below you can find a suggestion for the pull request body:

Hi all,

This pull request contains a backport of commit a1a56cf8 from the openjdk/jdk21u-dev repository.

The commit being backported was authored by Christoph Langer on 13 Jun 2024 and had no reviewers.

Thanks!

Please sign in to comment.