ef9ce3be1e
Tests that hardcode "127.0.0.1" fail in an environment where only IPv6 is available and IPv4 is not. Reviewed-by: chegar, dfuchs, michaelm
294 lines
11 KiB
Java
294 lines
11 KiB
Java
/*
|
|
* Copyright 2009, 2019, Google Inc. 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
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
import java.net.InetAddress;
|
|
import java.net.InetSocketAddress;
|
|
import java.net.SocketAddress;
|
|
import java.nio.channels.SelectionKey;
|
|
import java.nio.channels.Selector;
|
|
import java.nio.channels.ServerSocketChannel;
|
|
import java.nio.channels.SocketChannel;
|
|
import java.util.ArrayList;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Reproduces O(N^2) behavior of JDK6/7 select() call. This happens when
|
|
* a selector has many unprocessed updates to its interest set (e.g. adding
|
|
* OP_READ on a bunch of newly accepted sockets). The O(N^2) is triggered
|
|
* by cancelling a number of selection keys (or just closing a few sockets).
|
|
* In this case, select() will first go through the list of cancelled keys
|
|
* and try to deregister them. That deregistration is O(N^2) over the list
|
|
* of unprocessed updates to the interest set.
|
|
*
|
|
* <p> This O(N^2) behavior is a BUG in JVM and should be fixed.
|
|
*
|
|
* <p> The test first creates initCount connections, and adds them
|
|
* to the server epoll set. It then creates massCount connections,
|
|
* registers interest (causing updateList to be populated with massCount*2
|
|
* elements), but does not add them to epoll set (that would've cleared
|
|
* updateList). The test then closes initCount connections, thus populating
|
|
* deregistration queue. The subsequent call to selectNow() will first process
|
|
* deregistration queue, performing O(N^2) over updateList size,
|
|
* equal to massCount * 2.
|
|
*
|
|
* <p> Note that connect rate is artificially slowed down to compensate
|
|
* for what I believe is a Linux bug, where too high of a connection rate
|
|
* ends up in SYN's being dropped and then slow retransmits.
|
|
*
|
|
* @author Igor Chernyshev
|
|
*/
|
|
public class LotsOfCancels {
|
|
|
|
static long testStartTime;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
// the final select should run in less than 1000ms.
|
|
runTest(500, 2700, 1000);
|
|
}
|
|
|
|
static void log(String msg) {
|
|
System.out.println(getLogPrefix() + msg);
|
|
}
|
|
|
|
static String getLogPrefix() {
|
|
return durationMillis(testStartTime) + ": ";
|
|
}
|
|
|
|
/**
|
|
* Returns the elapsed time since startNanos, in milliseconds.
|
|
* @param startNanos the start time; this must be a value returned
|
|
* by {@link System.nanoTime}
|
|
*/
|
|
static long durationMillis(long startNanos) {
|
|
return (System.nanoTime() - startNanos) / (1000L * 1000L);
|
|
}
|
|
|
|
static void runTest(int initCount, int massCount, int maxSelectTime)
|
|
throws Exception {
|
|
testStartTime = System.nanoTime();
|
|
|
|
InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress(), 7359);
|
|
|
|
// Create server channel, add it to selector and run epoll_ctl.
|
|
log("Setting up server");
|
|
Selector serverSelector = Selector.open();
|
|
ServerSocketChannel server = ServerSocketChannel.open();
|
|
server.configureBlocking(false);
|
|
server.socket().bind(address, 5000);
|
|
server.register(serverSelector, SelectionKey.OP_ACCEPT);
|
|
serverSelector.selectNow();
|
|
|
|
log("Setting up client");
|
|
ClientThread client = new ClientThread(address);
|
|
client.start();
|
|
Thread.sleep(100);
|
|
|
|
// Set up initial set of client sockets.
|
|
log("Starting initial client connections");
|
|
client.connectClients(initCount);
|
|
Thread.sleep(500); // Wait for client connections to arrive
|
|
|
|
// Accept all initial client sockets, add to selector and run
|
|
// epoll_ctl.
|
|
log("Accepting initial connections");
|
|
List<SocketChannel> serverChannels1 =
|
|
acceptAndAddAll(serverSelector, server, initCount);
|
|
if (serverChannels1.size() != initCount) {
|
|
throw new Exception("Accepted " + serverChannels1.size() +
|
|
" instead of " + initCount);
|
|
}
|
|
serverSelector.selectNow();
|
|
|
|
// Set up mass set of client sockets.
|
|
log("Requesting mass client connections");
|
|
client.connectClients(massCount);
|
|
Thread.sleep(500); // Wait for client connections to arrive
|
|
|
|
// Accept all mass client sockets, add to selector and do NOT
|
|
// run epoll_ctl.
|
|
log("Accepting mass connections");
|
|
List<SocketChannel> serverChannels2 =
|
|
acceptAndAddAll(serverSelector, server, massCount);
|
|
if (serverChannels2.size() != massCount) {
|
|
throw new Exception("Accepted " + serverChannels2.size() +
|
|
" instead of " + massCount);
|
|
}
|
|
|
|
// Close initial set of sockets.
|
|
log("Closing initial connections");
|
|
closeAll(serverChannels1);
|
|
|
|
// Now get the timing of select() call.
|
|
log("Running the final select call");
|
|
long startTime = System.nanoTime();
|
|
serverSelector.selectNow();
|
|
long duration = durationMillis(startTime);
|
|
log("Init count = " + initCount +
|
|
", mass count = " + massCount +
|
|
", duration = " + duration + "ms");
|
|
|
|
if (duration > maxSelectTime) {
|
|
System.out.println
|
|
("\n\n\n\n\nFAILURE: The final selectNow() took " +
|
|
duration + "ms " +
|
|
"- seems like O(N^2) bug is still here\n\n");
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
static List<SocketChannel> acceptAndAddAll(Selector selector,
|
|
ServerSocketChannel server,
|
|
int expected)
|
|
throws Exception {
|
|
int retryCount = 0;
|
|
int acceptCount = 0;
|
|
List<SocketChannel> channels = new ArrayList<SocketChannel>();
|
|
while (channels.size() < expected) {
|
|
SocketChannel channel = server.accept();
|
|
if (channel == null) {
|
|
log("accept() returned null " +
|
|
"after accepting " + acceptCount + " more connections");
|
|
acceptCount = 0;
|
|
if (retryCount < 10) {
|
|
// See if more new sockets got stacked behind.
|
|
retryCount++;
|
|
Thread.sleep(500);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
retryCount = 0;
|
|
acceptCount++;
|
|
channel.configureBlocking(false);
|
|
channel.register(selector, SelectionKey.OP_READ);
|
|
channels.add(channel);
|
|
}
|
|
// Cause an additional updateList entry per channel.
|
|
for (SocketChannel channel : channels) {
|
|
channel.register(selector, SelectionKey.OP_WRITE);
|
|
}
|
|
return channels;
|
|
}
|
|
|
|
static void closeAll(List<SocketChannel> channels)
|
|
throws Exception {
|
|
for (SocketChannel channel : channels) {
|
|
channel.close();
|
|
}
|
|
}
|
|
|
|
static class ClientThread extends Thread {
|
|
private final SocketAddress address;
|
|
private final Selector selector;
|
|
private int connectionsNeeded;
|
|
private int totalCreated;
|
|
|
|
ClientThread(SocketAddress address) throws Exception {
|
|
this.address = address;
|
|
selector = Selector.open();
|
|
setDaemon(true);
|
|
}
|
|
|
|
void connectClients(int count) throws Exception {
|
|
synchronized (this) {
|
|
connectionsNeeded += count;
|
|
}
|
|
selector.wakeup();
|
|
}
|
|
|
|
@Override
|
|
public void run() {
|
|
try {
|
|
handleClients();
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
System.exit(1);
|
|
}
|
|
}
|
|
|
|
private void handleClients() throws Exception {
|
|
int selectCount = 0;
|
|
while (true) {
|
|
int createdCount = 0;
|
|
synchronized (this) {
|
|
if (connectionsNeeded > 0) {
|
|
|
|
while (connectionsNeeded > 0 && createdCount < 20) {
|
|
connectionsNeeded--;
|
|
createdCount++;
|
|
totalCreated++;
|
|
|
|
SocketChannel channel = SocketChannel.open();
|
|
channel.configureBlocking(false);
|
|
channel.connect(address);
|
|
if (!channel.finishConnect()) {
|
|
channel.register(selector,
|
|
SelectionKey.OP_CONNECT);
|
|
}
|
|
}
|
|
|
|
log("Started total of " +
|
|
totalCreated + " client connections");
|
|
Thread.sleep(200);
|
|
}
|
|
}
|
|
|
|
if (createdCount > 0) {
|
|
selector.selectNow();
|
|
} else {
|
|
selectCount++;
|
|
long startTime = System.nanoTime();
|
|
selector.select();
|
|
long duration = durationMillis(startTime);
|
|
log("Exited clientSelector.select(), loop #"
|
|
+ selectCount + ", duration = " + duration + "ms");
|
|
}
|
|
|
|
int keyCount = -1;
|
|
Iterator<SelectionKey> keys =
|
|
selector.selectedKeys().iterator();
|
|
while (keys.hasNext()) {
|
|
SelectionKey key = keys.next();
|
|
synchronized (key) {
|
|
keyCount++;
|
|
keys.remove();
|
|
if (!key.isValid()) {
|
|
log("Ignoring client key #" + keyCount);
|
|
continue;
|
|
}
|
|
int readyOps = key.readyOps();
|
|
if (readyOps == SelectionKey.OP_CONNECT) {
|
|
key.interestOps(0);
|
|
((SocketChannel) key.channel()).finishConnect();
|
|
} else {
|
|
log("readyOps() on client key #" + keyCount +
|
|
" returned " + readyOps);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|