Path: blob/master/test/jdk/java/net/HugeDataTransferTest.java
41145 views
/*1* Copyright (c) 2017, Oracle and/or its affiliates. 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*/2223/**24* @test25* @bug 818507226* @summary network006 times out in many configs in JDK10-hs nightly27* @run main/othervm/manual HugeDataTransferTest 128*/29import java.io.FileNotFoundException;30import java.io.IOException;31import java.io.InputStream;32import java.io.OutputStream;33import java.io.PrintStream;34import java.net.InetAddress;35import java.net.ServerSocket;36import java.net.Socket;37import java.net.UnknownHostException;38import java.nio.file.Path;39import java.nio.file.Paths;40import java.util.Random;4142/**43* This test makes huge number of data transfers between 2 Java virtual machines44* using the TCP/IP protocol, and checks if those data are transfered correctly.45* Both client and server VMs run on the same local computer and attach TCP/IP46* sockets to the local host, or to the loopback domain47* ``<code>localhost</code>'' (having IP address <code>127.0.0.1</code>).48*49* <p>50* In this test, 128 client/server connections are established. Once a51* connection is established, client passes a data parcel to server, and server52* reads that parcel and checks if it is same as expected (byte-to-byte equality53* is desired). Then server passes (some other) parcel to the client, and client54* reads and verifies those bytes. This ping-pong game is repeated 128 times;55* and after that each pair of sockets checks if there are no extra bytes56* accedentally passed through their connection.57*58* <p>59* Parcels lengths and contents are chosen randomly, and average parcel length60* is 128 bytes. So totally, each pair of sockets passes ~16Kb of data to each61* other, and thus ~32Kb of data are transfered by each sockets pair. Totally,62* ~4Mb of data are transfered by all client/server pairs.63*64* @author vtewari65*/66public class HugeDataTransferTest {6768/**69* Timeout for TCP/IP sockets (currently set to 1 min).70*/71private static int SO_TIMEOUT;// = 2*60*1000;7273/**74* Maximal number of connections this test should open simultaneously.75*/76private final static int MAX_CONNECTIONS = 128;7778/**79* Check few more connections to make sure that MAX_CONNECTIONS are safe.80*/81private final static int CONNECTIONS_RESERVE = 10;8283/**84* The test used to fail with connection reset by peer set to 50. (and once85* in a three if it was set to 10). So now we set it to MAX_CONNECTIONS86* (128).87*/88private final static int BACKLOG_QUEUE_LENGTH = MAX_CONNECTIONS;8990/**91* Number of parcels to be sent/recieved.92*/93private final static int DATA_PARCELS = 128;9495/**96* Maximal length of data parcel to be sent/recieved (it equals to 256 bytes97* now).98*/99private final static int MAX_PARCEL = 1 << 8;100101/**102* Either actually display optional reports or not.103*/104static private final boolean DEBUG_MODE = false;105106/**107* How many IP sockets can we open simultaneously? Check if108* <code>MAX_CONNECTIONS</code> connections can be open simultaneously.109*/110private static int detectOSLimitation() {111final int CONNECTIONS_TO_TRY = MAX_CONNECTIONS + CONNECTIONS_RESERVE;112display("--- Trying to open " + CONNECTIONS_TO_TRY + " connections:");113114InetAddress address;115ServerSocket serverSocket;116try {117address = InetAddress.getLocalHost();118int anyPort = 0;119int defaultBacklog = BACKLOG_QUEUE_LENGTH;120serverSocket = new ServerSocket(anyPort, defaultBacklog, address);121} catch (IOException ioe) {122throw new Error("FATAL error while loading the test: " + ioe);123}124display(serverSocket.toString());125126Socket server[] = new Socket[CONNECTIONS_TO_TRY];127Socket client[] = new Socket[CONNECTIONS_TO_TRY];128129int i, port = serverSocket.getLocalPort();130for (i = 0; i < CONNECTIONS_TO_TRY; i++) {131try {132client[i] = new Socket(address, port);133display(">Open: client[" + i + "] = " + client[i]);134server[i] = serverSocket.accept();135display(">Open: server[" + i + "] = " + server[i]);136} catch (IOException ioe) {137display(">OOPS! -- failed to open connection #" + i);138break;139}140}141display("> Could open "142+ (i < CONNECTIONS_TO_TRY ? "only " : "") + i + " connections.");143display(">Closing them:");144for (int j = 0; j < i; j++) {145try {146server[j].close();147client[j].close();148} catch (IOException ioe) {149throw new Error("FATAL error while loading the test: " + ioe);150}151}152display(">OK.");153int safeConnections = i - CONNECTIONS_RESERVE;154if (safeConnections < 1) {155safeConnections = 1;156}157if (safeConnections < MAX_CONNECTIONS) {158complain("------------------------- CAUTION: -------------------");159complain("While checking the OS limitations, the test found that");160complain("only " + i + " TCP/IP socket connections could be safely open");161complain("simultaneously. However, possibility to open at least");162complain("" + MAX_CONNECTIONS + "+" + CONNECTIONS_RESERVE163+ " connections were expected.");164complain("");165complain("So, the test will check only " + safeConnections + " connection"166+ (safeConnections == 1 ? "" : "s") + " which seem");167complain("safe to be open simultaneously.");168complain("------------------------------------------------------");169}170return safeConnections;171}172173//----------------------------------------------------------------//174/**175* Re-calls to the method <code>run(args[],out)</code> actually performing176* the test. After <code>run(args[],out)</code> stops, follow JDK-like177* convention for exit codes. I.e.: stop with exit status 95 if the test has178* passed, or with status 97 if the test has failed.179*180* @see #run(String[],PrintStream)181*/182public static void main(String args[]) {183int exitCode = run(args, System.out);184System.exit(exitCode + 95);185// JCK-like exit status.186}187188public static int run(String args[], PrintStream out) {189HugeDataTransferTest.out = out;190191//192// Get the Internet address of the local machine.193//194InetAddress address = null;195try {196address = InetAddress.getLocalHost();197} catch (UnknownHostException exception) {198complain(exception.toString());199return 2; // FAILED200}201display("Host: " + address);202203//204// Detect if it is safe to open MAX_CONNETIONS simultaneously:205//206final int CONNECTIONS = detectOSLimitation();207208//209// Assign ServerSocket, and start client VM which should open210// the prescribed number of CONNECTIONS to that ServerSocket.211//212ServerSocket serverSocket;213try {214final int anyPort = 0;215final int defaultBacklog = BACKLOG_QUEUE_LENGTH;216serverSocket = new ServerSocket(anyPort, defaultBacklog, address);217} catch (IOException exception) {218complain("Cannot assign a ServerSocket on: " + address);219return 2;220}221222//223// Start the client process on different VM.224//225String jdkPath = System.getProperty("test.jdk");226Path toolName = Paths.get("bin", "java" + (isWindows() ? ".exe" : ""));227Path jdkTool = Paths.get(jdkPath, toolName.toString());228229String IPAddress = address.getHostAddress();230int localPort = serverSocket.getLocalPort();231String arguments = " " + CONNECTIONS + " " + IPAddress + " " + localPort;232//String command = args[0] + " " + network006.class.getName() + "$Client " + arguments;233String command = jdkTool.toAbsolutePath().toString() + " " + Client.class.getName() + " " + arguments;234try {235SO_TIMEOUT = Integer.parseInt(args[0]) * 60 * 1000;236} catch (NumberFormatException e) {237complain("Wrong timeout argument: " + e);238return 2;239}240241Runtime runtime = Runtime.getRuntime();242243Process client = null;244IORedirector redirectOut = null;245IORedirector redirectErr = null;246247try {248// Start clients on different JVM:249client = runtime.exec(command);250251// Provide clients with access to stderr and stdout:252InputStream clientOut = client.getInputStream();253InputStream clientErr = client.getErrorStream();254redirectOut = new IORedirector(clientOut, DEBUG_MODE ? out : null);255redirectErr = new IORedirector(clientErr, out);256redirectOut.start();257redirectErr.start();258259} catch (IOException exception) {260complain("Failed to start client: " + exception);261return 2;262}263//264// Start the server threads (and let them establish connections):265//266267Server server[] = new Server[CONNECTIONS];268for (int i = 0; i < CONNECTIONS; i++) {269server[i] = new Server(serverSocket);270display("Server #" + i + ": " + server[i]);271server[i].start();272}273274//275// Wait for the servers and the clients:276//277boolean testFailed = false;278279try {280client.waitFor();281int clientStatus = client.exitValue();282display("Client VM exitCode=" + clientStatus);283284// Let I/O redirectors to flush:285if (redirectOut.isAlive()) {286redirectOut.join();287}288if (redirectErr.isAlive()) {289redirectErr.join();290}291292// If client has crashed, also terminate the server (to avoid hangup).293if (clientStatus != 95) {294complain("Client VM has crashed: exit status=" + clientStatus);295testFailed = true;296}297298// Client has finished OK; wait for the server.299for (int i = 0; i < CONNECTIONS; i++) {300display("Server: waiting for #" + i);301if (server[i].isAlive()) {302display("Server #" + i + ": (joining...)" + server[i]);303server[i].join();304}305if (server[i].exception != null) {306if (server[i].message != null) {307complain("Server #" + i + "(finished): with message:" + server[i].message);308}309310complain("Server #" + i + "(finished): " + server[i].exception);311server[i].exception.printStackTrace(out);312out.flush();313// complain("Server #"+i+": "+server[i].exception.getStackTrace());314testFailed = true;315}316}317318} catch (InterruptedException exception) {319complain("Test interrupted: " + exception);320testFailed = true;321}322323if (testFailed) {324complain("Test failed.");325} else {326display("Test passed.");327}328return testFailed ? 2 : 0;329}330331private static boolean isWindows() {332return System.getProperty("os.name").toLowerCase().startsWith("win");333}334//----------------------------------------------------------------//335/**336* Log stream for error messages and/or (optional) execution trace.337*/338private static PrintStream out;339340/**341* Print error message.342*/343private static synchronized void complain(Object message) {344out.println("# " + message);345out.flush();346}347348;349350/**351* Display optional report: comment ca va?352*/353private static synchronized void display(Object report) {354if (DEBUG_MODE) {355out.println(report.toString());356}357out.flush(); //todo shouldn't this be inside if??358}359360;361362//----------------------------------------------------------------//363364/**365* Server thread should reply to data parcels sent by Client VM.366*/367private static class Server extends Thread {368369/**370* The socket is assigned at the Server instantiation.371*/372private ServerSocket serverSocket;373374/**375* The socket is assigned at the Server runtime.376*/377private Socket socket;378379/**380* Display the server socket.381*/382@Override383public String toString() {384385return "ServerSocket: " + serverSocket.toString();386// + " socket: " + socket.toString();387}388389/**390* Which port is this socket listening?391*/392int getPort() {393return serverSocket.getLocalPort();394}395396/**397* Find some free port at the given <code>address</code> and attach new398* server to hear that port. // lidsten to??399*/400public Server(ServerSocket serverSocket) {401this.serverSocket = serverSocket;402}403404/**405* Exception just arisen while the server was working, or406* <code>null</code> if it was OK with the server.407*/408Exception exception = null;409String message = null;410411/**412* Accept connection, then reply to client's parcels.413*/414@Override415public void run() {416try {417socket = serverSocket.accept();418socket.setSoTimeout(SO_TIMEOUT);419420InputStream istream = socket.getInputStream();421OutputStream ostream = socket.getOutputStream();422423Random random = new Random(getPort());424425for (int i = 0; i < DATA_PARCELS; i++) {426Parcel etalon = new Parcel(random);427message = "reading parcel number " + i;428Parcel sample = new Parcel(istream); // read429if (!sample.equals(etalon)) {430complain("Server thread for port #"431+ getPort() + " got unexpected parcel:\n"432+ "sample=" + sample + "\n"433+ "etalon=" + etalon);434throw new TestFailure( //received??435"server has read unexpected parcel");436}437message = "sending parcel number " + i;438etalon.send(ostream);439ostream.flush();440}441442int datum = istream.read(); // wait for client close()443if (datum >= 0) {444throw new TestFailure(445"server has read ambigous byte: " + datum);446}447448ostream.close(); // implies: socket.close();449450} catch (Exception oops) {451exception = oops;452}453}454}455456//----------------------------------------------------------------//457/**458* Client VM should send data parcels to Server VM and recieve and verify459* the server's replies.460*/461private static class Client extends Thread {462463/**464* This thread uses the single client socket.465*/466private Socket socket;467468/**469* Address and port of this socket.470*/471@Override472public String toString() {473return socket.toString();474}475476/**477* Did the thread failed? If yes, what is the failure's reason.478*/479Exception exception = null;480String message = null;481482public static java.io.PrintStream complainStream = System.out;483public static java.io.PrintStream displayStream = System.err;484485/**486* Connect client socket on the given <code>address</code> and487* <code>port</code>.488*/489Client(InetAddress address, int port) throws IOException {490socket = new Socket(address, port);491socket.setSoTimeout(SO_TIMEOUT);492}493494/**495* What is the port number this socket is listening for?496*/497int getPort() {498return socket.getPort();499}500501/**502* Establish connection, then read/respond <code>DATA_PARCELS</code>503* parcels of random data. Set initial seed for pseudo-random numbers504* generator to the value of the local port number.505*506* @see #DATA_PARCELS507* @see #getPort()508*/509@Override510public void run() {511try {512InputStream istream = socket.getInputStream();513OutputStream ostream = socket.getOutputStream();514515Random random = new Random(getPort());516// suggested by Oleg -- to avoid race conditions517/* try{518Thread.sleep(500);519}520catch (java.lang.InterruptedException e)521{522}*/523524for (int i = 0; i < DATA_PARCELS; i++) {525Parcel etalon = new Parcel(random);526message = "sending parcel number: " + i;527etalon.send(ostream);528ostream.flush();529530message = "reading parcel number: " + i;531Parcel sample = new Parcel(istream); // read532if (!sample.equals(etalon)) {533complain("Client thread for port #"534+ getPort() + " got unexpected parcel:\n"535+ "sample=" + sample + "\n"536+ "etalon=" + etalon);537throw new TestFailure(538"parcel context is unexpected to client");539}540}541542if (istream.available() > 0) {543int datum = istream.read();544throw new TestFailure(545"client has read ambigous byte: " + datum);546}547ostream.close(); // implies: socket.close()548549} catch (Exception oops) {550exception = oops;551}552}553554/**555* Establish lots of connections to server socket, attack servers with556* huge data parcels, and check if they reply correctly. The number of557* connections to try, the address and port number for the server socket558* are passed through <code>args[]</code>, like:559* <pre>560* java network006$Client connections_to_try address port561* </pre>562*/563public static void main(String args[]) {564if (DEBUG_MODE) {565try {566String filename = "Client" + ((args.length == 3) ? args[2] : "new");567displayStream = new PrintStream(filename + ".out");568complainStream = new PrintStream(filename + ".err");569} catch (FileNotFoundException exception) {570complain(exception);571}572573}574575if (args.length != 3) {576complain("Client expects 3 paramenets:");577complain(" java " + Client.class.getName() + " connections_to_try address port");578exit(1); // FAILED579}580581int CONNECTIONS = Integer.parseInt(args[0]);582display("Client VM: will try " + CONNECTIONS + " connections.");583InetAddress address;584try {585address = InetAddress.getByName(args[1]);586} catch (UnknownHostException exception) {587address = null;588complain("Client: cannot find host: \"" + args[1] + "\"");589exit(4);590}591display("Client: host to contact: " + address);592int port = Integer.parseInt(args[2]);593display("Client: port to contact: " + port);594595//596// Establish connections, and start client processes:597//598Client client[] = new Client[CONNECTIONS];599for (int i = 0; i < CONNECTIONS; i++) {600try {601client[i] = new Client(address, port);602display("Client #" + i + ": " + client[i]);603604} catch (IOException ioe) {605complain("Client #" + i + "(creation): " + ioe);606ioe.printStackTrace(complainStream);607complainStream.flush();608// complain("Client #" + i + "(creation): " + ioe.getStackTrace());609exit(3);610}611}612613for (int i = 0; i < CONNECTIONS; i++) {614client[i].start();615}616617//618// Wait until testing is not finished:619//620int status = 0;621for (int i = 0; i < CONNECTIONS; i++) {622display("Client: waiting for #" + i);623if (client[i].isAlive()) {624display("Client #" + i + ": (joining...)" + client[i]);625626try {627client[i].join();628} catch (InterruptedException ie) {629complain("Client #" + i + ": " + ie);630status = 3;631}632}633if (client[i].exception != null) {634if (client[i].message != null) {635complain("Client #" + i + "(finished) with message: " + client[i].message);636}637complain("Client #" + i + "(finished): " + client[i].exception);638client[i].exception.printStackTrace(complainStream);639complainStream.flush();640if (status == 0) {641status = 2;642}643}644}645646exit(status);647}648649/**650* Print error message.651*/652private static synchronized void complain(Object message) {653complainStream.println("# " + message);654complainStream.flush();655}656657/**658* Display execution trace.659*/660private static synchronized void display(Object message) {661if (!DEBUG_MODE) {662return;663}664displayStream.println(message.toString());665displayStream.flush();666}667668/**669* Exit with JCK-like status.670*/671private static void exit(int exitCode) {672int status = exitCode + 95;673// display("Client: exiting with code=" + status);674System.exit(status);675}676}677678/**679* Two of such threads should redirect <code>out</code> and <code>err</code>680* streams of client VM.681*/682private static class IORedirector extends Thread {683684/**685* Source stream.686*/687InputStream in;688/**689* Destination stream.690*/691OutputStream out;692693/**694* Redirect <code>in</code> to <code>out</code>.695*/696public IORedirector(InputStream in, OutputStream out) {697this.in = in;698this.out = out;699}700701/**702* Read input stream until the EOF, and write everithing to output703* stream. If output stream is assigned to <code>null</code>, do not704* print anything, but read the input stream anywhere.705*/706@Override707public void run() {708try {709for (;;) {710int symbol = in.read();711if (symbol < 0) {712break; // EOF713}714if (out != null) {715out.write(symbol);716}717}718719if (out != null) {720out.flush();721}722723} catch (IOException exception) {724throw new TestFailure("IORedirector exception: " + exception);725}726}727}728729//----------------------------------------------------------------//730/**731* A data parcel to be sent/recieved between Client VM and Server thread.732* When data parcel is sent, first 4 bytes are transfered which encode the733* <code>int</code> number equal to size of the parcel minus 1. I.e.: if734* number of data bytes in the parcel's contents is <code>N</code>, then the735* first 4 bytes encode the number <code>N-1</code>. After that, the736* parcel's contents bytes are transered.737*/738static class Parcel {739740private final byte[] parcel;741742/**743* Display all bytes as integer values from 0 to 255; or return744* ``<tt>null</tt>'' if this Parcel is not yet initialized.745*/746@Override747public String toString() {748if (parcel == null) {749return "null";750}751String s = "{";752for (int i = 0; i < parcel.length; i++) {753s += (i > 0 ? ", " : "") + ((int) parcel[i] & 0xFF);754}755return s + "}";756}757758/**759* Generate new <code>parcel[]</code> array using the given760* <code>random</code> numbers generator. Client and Server threads761* should use identical <code>random</code> generators, so that those762* threads could generate equal data parcels and check the parcel just763* transfered.764*/765public Parcel(Random random) {766int size = random.nextInt(MAX_PARCEL) + 1;767parcel = new byte[size];768for (int i = 0; i < size; i++) {769parcel[i] = (byte) random.nextInt(256);770}771}772773;774775/**776* Read exactly <code>size</code> bytes from the <code>istream</code>777* if possible, or throw <code>TestFailure</code> if unexpected end of778* <code>istream</code> occurs.779*/780private static byte[] readBytes(int size, InputStream istream)781throws IOException {782783byte data[] = new byte[size];784for (int i = 0; i < size; i++) {785int datum = istream.read();786if (datum < 0) {787throw new TestFailure(788"unexpected EOF: have read: " + i + " bytes of " + size);789}790data[i] = (byte) datum;791}792return data;793}794795/**796* Read 4 bytes from <code>istream</code> and threat them to encode size797* of data parcel following these 4 bytes.798*/799private static int getSize(InputStream istream) throws IOException {800byte data[] = readBytes(4, istream);801int data0 = (int) data[0] & 0xFF;802int data1 = (int) data[1] & 0xFF;803int data2 = (int) data[2] & 0xFF;804int data3 = (int) data[3] & 0xFF;805int sizeWord = data0 + (data1 << 8) + (data2 << 16) + (data3 << 24);806int size = sizeWord + 1;807if (size <= 0) {808throw new TestFailure("illegal size: " + size);809}810return size;811}812813/**814* Send 4 bytes encoding actual size of the parcel just to be815* transfered.816*/817private static void putSize(OutputStream ostream, int size)818throws IOException {819820if (size <= 0) {821throw new TestFailure("illegal size: " + size);822}823824int sizeWord = size - 1;825byte data[] = new byte[4];826data[0] = (byte) sizeWord;827data[1] = (byte) (sizeWord >> 8);828data[2] = (byte) (sizeWord >> 16);829data[3] = (byte) (sizeWord >> 24);830ostream.write(data);831}832833/**834* Recieve data parcel.835*/836public Parcel(InputStream istream) throws IOException {837int size = getSize(istream);838parcel = readBytes(size, istream);839}840841/**842* Send <code>this</code> data parcel.843*/844public void send(OutputStream ostream) throws IOException {845int size = parcel.length;846putSize(ostream, size);847ostream.write(parcel);848}849850/**851* Check byte-to-byte equality between <code>this</code> and the852* <code>other</code> parcels.853*/854public boolean equals(Parcel other) {855if (this.parcel.length != other.parcel.length) {856return false;857}858int size = parcel.length;859for (int i = 0; i < size; i++) {860if (this.parcel[i] != other.parcel[i]) {861return false;862}863}864return true;865}866}867868/**869* Server or Client may throw this exception to report the test failure.870*/871static class TestFailure extends RuntimeException {872873/**874* Report particular <code>purpose</code> of the test failure.875*/876public TestFailure(String purpose) {877super(purpose);878}879}880}881882883