Path: blob/master/test/jdk/java/nio/channels/Selector/LotsOfCancels.java
41153 views
/*1* Copyright 2009, 2019, Google Inc. All Rights Reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation.7*8* This code is distributed in the hope that it will be useful, but WITHOUT9* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or10* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License11* version 2 for more details (a copy is included in the LICENSE file that12* accompanied this code).13*14* You should have received a copy of the GNU General Public License version15* 2 along with this work; if not, write to the Free Software Foundation,16* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.17*18* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA19* or visit www.oracle.com if you need additional information or have any20* questions.21*/2223import java.net.InetAddress;24import java.net.InetSocketAddress;25import java.net.SocketAddress;26import java.nio.channels.SelectionKey;27import java.nio.channels.Selector;28import java.nio.channels.ServerSocketChannel;29import java.nio.channels.SocketChannel;30import java.util.ArrayList;31import java.util.Iterator;32import java.util.List;3334/**35* Reproduces O(N^2) behavior of JDK6/7 select() call. This happens when36* a selector has many unprocessed updates to its interest set (e.g. adding37* OP_READ on a bunch of newly accepted sockets). The O(N^2) is triggered38* by cancelling a number of selection keys (or just closing a few sockets).39* In this case, select() will first go through the list of cancelled keys40* and try to deregister them. That deregistration is O(N^2) over the list41* of unprocessed updates to the interest set.42*43* <p> This O(N^2) behavior is a BUG in JVM and should be fixed.44*45* <p> The test first creates initCount connections, and adds them46* to the server epoll set. It then creates massCount connections,47* registers interest (causing updateList to be populated with massCount*248* elements), but does not add them to epoll set (that would've cleared49* updateList). The test then closes initCount connections, thus populating50* deregistration queue. The subsequent call to selectNow() will first process51* deregistration queue, performing O(N^2) over updateList size,52* equal to massCount * 2.53*54* <p> Note that connect rate is artificially slowed down to compensate55* for what I believe is a Linux bug, where too high of a connection rate56* ends up in SYN's being dropped and then slow retransmits.57*58* @author Igor Chernyshev59*/60public class LotsOfCancels {6162static long testStartTime;6364public static void main(String[] args) throws Exception {65// the final select should run in less than 1000ms.66runTest(500, 2700, 1000);67}6869static void log(String msg) {70System.out.println(getLogPrefix() + msg);71}7273static String getLogPrefix() {74return durationMillis(testStartTime) + ": ";75}7677/**78* Returns the elapsed time since startNanos, in milliseconds.79* @param startNanos the start time; this must be a value returned80* by {@link System.nanoTime}81*/82static long durationMillis(long startNanos) {83return (System.nanoTime() - startNanos) / (1000L * 1000L);84}8586static void runTest(int initCount, int massCount, int maxSelectTime)87throws Exception {88testStartTime = System.nanoTime();8990InetSocketAddress address = new InetSocketAddress(InetAddress.getLoopbackAddress(), 7359);9192// Create server channel, add it to selector and run epoll_ctl.93log("Setting up server");94Selector serverSelector = Selector.open();95ServerSocketChannel server = ServerSocketChannel.open();96server.configureBlocking(false);97server.socket().bind(address, 5000);98server.register(serverSelector, SelectionKey.OP_ACCEPT);99serverSelector.selectNow();100101log("Setting up client");102ClientThread client = new ClientThread(address);103client.start();104Thread.sleep(100);105106// Set up initial set of client sockets.107log("Starting initial client connections");108client.connectClients(initCount);109Thread.sleep(500); // Wait for client connections to arrive110111// Accept all initial client sockets, add to selector and run112// epoll_ctl.113log("Accepting initial connections");114List<SocketChannel> serverChannels1 =115acceptAndAddAll(serverSelector, server, initCount);116if (serverChannels1.size() != initCount) {117throw new Exception("Accepted " + serverChannels1.size() +118" instead of " + initCount);119}120serverSelector.selectNow();121122// Set up mass set of client sockets.123log("Requesting mass client connections");124client.connectClients(massCount);125Thread.sleep(500); // Wait for client connections to arrive126127// Accept all mass client sockets, add to selector and do NOT128// run epoll_ctl.129log("Accepting mass connections");130List<SocketChannel> serverChannels2 =131acceptAndAddAll(serverSelector, server, massCount);132if (serverChannels2.size() != massCount) {133throw new Exception("Accepted " + serverChannels2.size() +134" instead of " + massCount);135}136137// Close initial set of sockets.138log("Closing initial connections");139closeAll(serverChannels1);140141// Now get the timing of select() call.142log("Running the final select call");143long startTime = System.nanoTime();144serverSelector.selectNow();145long duration = durationMillis(startTime);146log("Init count = " + initCount +147", mass count = " + massCount +148", duration = " + duration + "ms");149150if (duration > maxSelectTime) {151System.out.println152("\n\n\n\n\nFAILURE: The final selectNow() took " +153duration + "ms " +154"- seems like O(N^2) bug is still here\n\n");155System.exit(1);156}157}158159static List<SocketChannel> acceptAndAddAll(Selector selector,160ServerSocketChannel server,161int expected)162throws Exception {163int retryCount = 0;164int acceptCount = 0;165List<SocketChannel> channels = new ArrayList<SocketChannel>();166while (channels.size() < expected) {167SocketChannel channel = server.accept();168if (channel == null) {169log("accept() returned null " +170"after accepting " + acceptCount + " more connections");171acceptCount = 0;172if (retryCount < 10) {173// See if more new sockets got stacked behind.174retryCount++;175Thread.sleep(500);176continue;177}178break;179}180retryCount = 0;181acceptCount++;182channel.configureBlocking(false);183channel.register(selector, SelectionKey.OP_READ);184channels.add(channel);185}186// Cause an additional updateList entry per channel.187for (SocketChannel channel : channels) {188channel.register(selector, SelectionKey.OP_WRITE);189}190return channels;191}192193static void closeAll(List<SocketChannel> channels)194throws Exception {195for (SocketChannel channel : channels) {196channel.close();197}198}199200static class ClientThread extends Thread {201private final SocketAddress address;202private final Selector selector;203private int connectionsNeeded;204private int totalCreated;205206ClientThread(SocketAddress address) throws Exception {207this.address = address;208selector = Selector.open();209setDaemon(true);210}211212void connectClients(int count) throws Exception {213synchronized (this) {214connectionsNeeded += count;215}216selector.wakeup();217}218219@Override220public void run() {221try {222handleClients();223} catch (Throwable e) {224e.printStackTrace();225System.exit(1);226}227}228229private void handleClients() throws Exception {230int selectCount = 0;231while (true) {232int createdCount = 0;233synchronized (this) {234if (connectionsNeeded > 0) {235236while (connectionsNeeded > 0 && createdCount < 20) {237connectionsNeeded--;238createdCount++;239totalCreated++;240241SocketChannel channel = SocketChannel.open();242channel.configureBlocking(false);243channel.connect(address);244if (!channel.finishConnect()) {245channel.register(selector,246SelectionKey.OP_CONNECT);247}248}249250log("Started total of " +251totalCreated + " client connections");252Thread.sleep(200);253}254}255256if (createdCount > 0) {257selector.selectNow();258} else {259selectCount++;260long startTime = System.nanoTime();261selector.select();262long duration = durationMillis(startTime);263log("Exited clientSelector.select(), loop #"264+ selectCount + ", duration = " + duration + "ms");265}266267int keyCount = -1;268Iterator<SelectionKey> keys =269selector.selectedKeys().iterator();270while (keys.hasNext()) {271SelectionKey key = keys.next();272synchronized (key) {273keyCount++;274keys.remove();275if (!key.isValid()) {276log("Ignoring client key #" + keyCount);277continue;278}279int readyOps = key.readyOps();280if (readyOps == SelectionKey.OP_CONNECT) {281key.interestOps(0);282((SocketChannel) key.channel()).finishConnect();283} else {284log("readyOps() on client key #" + keyCount +285" returned " + readyOps);286}287}288}289}290}291}292}293294295