/* * Copyright (c) 2003, 2012, 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 * 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. */ /* @test * * @summary The juicer is the classic RMI stress test. The juicer makes * a large number of concurrent, long running, remote method invocations * between many threads which have exported remote objects. These * threads use remote objects that carry on deep "two party" * recursion. The juicer relies on Distributed Garbage Collection to * unexport these remote objects when no more references are held to them. * The two parties in the recursion are OrangeImpl and * OrangeEchoImpl. OrangeImpl checks the base case of the recursion * so that the program will exit. * * When the AppleUserImpl.main() method is invoked, the class binds an * instance of itself in a registry. A second server process, * an ApplicationServer, is started which looks up the recently * bound AppleUser object. This server is either started up in * the same VM or can optionally be started in a separate VM on the * same host or on a different host. When this test is run on the * RMI profile, ApplicationServer must be started by AppleUserImpl * and the complete juicer runs in a single process. * * The second server process instructs the AppleUserImpl to "use" some apples. * AppleUserImpl creates a new thread for each apple. These threads * initiate the two party recursion. * * Each recursive call nests to a depth determined by this * expression: (2 + Math.abs(random.nextInt() % (maxLevel + 1)), * where maxLevel is a command line parameter. Thus each recursive * call nests a random number of levels between 2 and maxLevel. * * The test ends when an exception is encountered or the stop time * has been reached. * * @library ../../testlibrary * * @build Apple AppleEvent AppleImpl AppleUserImpl * @build Orange OrangeEcho OrangeEchoImpl OrangeImpl * @build ApplicationServer * @build TestLibrary * * @run main/othervm/policy=security.policy AppleUserImpl -seconds 30 * * @author Peter Jones, Nigel Daley */ import java.rmi.NoSuchObjectException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.util.Random; import java.util.logging.Level; import java.util.logging.Logger; /** * The AppleUserImpl class implements the behavior of the remote * "apple user" objects exported by the server. The application server * passes each of its remote "apple" objects to an apple user, and an * AppleUserThread is created for each apple. */ public class AppleUserImpl extends UnicastRemoteObject implements AppleUser { private static int registryPort = -1; private static final Logger logger = Logger.getLogger("reliability.appleuser"); private static int threadNum = 0; private static long testDuration = 0; private static int maxLevel = 7; private static Exception status = null; private static boolean finished = false; private static boolean startTestNotified = false; private static final Random random = new Random(); private static final Object lock = new Object(); public AppleUserImpl() throws RemoteException { } /** * Allows the other server process to indicate that it is ready * to start "juicing". */ public synchronized void startTest() throws RemoteException { startTestNotified = true; this.notifyAll(); } /** * Allows the other server process to report an exception to this * process and thereby terminate the test. */ public void reportException(Exception status) throws RemoteException { synchronized (lock) { this.status = status; lock.notifyAll(); } } /** * "Use" supplied apple object. Create an AppleUserThread to * stress it out. */ public synchronized void useApple(Apple apple) throws RemoteException { String threadName = Thread.currentThread().getName(); logger.log(Level.FINEST, threadName + ": AppleUserImpl.useApple(): BEGIN"); AppleUserThread t = new AppleUserThread("AppleUserThread-" + (++threadNum), apple); t.start(); logger.log(Level.FINEST, threadName + ": AppleUserImpl.useApple(): END"); } /** * The AppleUserThread class repeatedly invokes calls on its associated * Apple object to stress the RMI system. */ class AppleUserThread extends Thread { final Apple apple; public AppleUserThread(String name, Apple apple) { super(name); this.apple = apple; } public void run() { int orangeNum = 0; long stopTime = System.currentTimeMillis() + testDuration; Logger logger = Logger.getLogger("reliability.appleuserthread"); try { do { // loop until stopTime is reached /* * Notify apple with some apple events. This tests * serialization of arrays. */ int numEvents = Math.abs(random.nextInt() % 5); AppleEvent[] events = new AppleEvent[numEvents]; for (int i = 0; i < events.length; i++) { events[i] = new AppleEvent(orangeNum % 3); } apple.notify(events); /* * Request a new orange object be created in * the application server. */ Orange orange = apple.newOrange( "Orange(" + getName() + ")-" + (++orangeNum)); /* * Create a large message of random ints to pass to orange. */ int msgLength = 1000 + Math.abs(random.nextInt() % 3000); int[] message = new int[msgLength]; for (int i = 0; i < message.length; i++) { message[i] = random.nextInt(); } /* * Invoke recursive call on the orange. Base case * of recursion inverts messgage. */ OrangeEchoImpl echo = new OrangeEchoImpl( "OrangeEcho(" + getName() + ")-" + orangeNum); int[] response = orange.recurse(echo, message, 2 + Math.abs(random.nextInt() % (maxLevel + 1))); /* * Verify message was properly inverted and not corrupted * through all the recursive method invocations. */ if (response.length != message.length) { throw new RuntimeException( "ERROR: CORRUPTED RESPONSE: " + "wrong length of returned array " + "(should be " + message.length + ", is " + response.length + ")"); } for (int i = 0; i < message.length; i++) { if (~message[i] != response[i]) { throw new RuntimeException( "ERROR: CORRUPTED RESPONSE: " + "at element " + i + "/" + message.length + " of returned array (should be " + Integer.toHexString(~message[i]) + ", is " + Integer.toHexString(response[i]) + ")"); } } try { Thread.sleep(Math.abs(random.nextInt() % 10) * 1000); } catch (InterruptedException e) { } } while (System.currentTimeMillis() < stopTime); } catch (Exception e) { status = e; } finished = true; synchronized (lock) { lock.notifyAll(); } } } private static void usage() { System.err.println("Usage: AppleUserImpl [-hours | " + "-seconds ]"); System.err.println(" [-maxLevel ]"); System.err.println(" [-othervm]"); System.err.println(" [-exit]"); System.err.println(" hours The number of hours to run the juicer."); System.err.println(" The default is 0 hours."); System.err.println(" seconds The number of seconds to run the juicer."); System.err.println(" The default is 0 seconds."); System.err.println(" maxLevel The maximum number of levels to "); System.err.println(" recurse on each call."); System.err.println(" The default is 7 levels."); System.err.println(" othervm If present, the VM will wait for the"); System.err.println(" ApplicationServer to start in"); System.err.println(" another process."); System.err.println(" The default is to run everything in"); System.err.println(" a single VM."); System.err.println(" exit If present, the VM will call"); System.err.println(" System.exit() when main() finishes."); System.err.println(" The default is to not call"); System.err.println(" System.exit()."); System.err.println(); } /** * Entry point for the "juicer" server process. Create and export * an apple user implementation in an rmiregistry running on localhost. */ public static void main(String[] args) { String durationString = null; boolean othervm = false; boolean exit = false; try { // parse command line args for (int i = 0; i < args.length ; i++ ) { String arg = args[i]; if (arg.equals("-hours")) { if (durationString != null) { usage(); } i++; int hours = Integer.parseInt(args[i]); durationString = hours + " hours"; testDuration = hours * 60 * 60 * 1000; } else if (arg.equals("-seconds")) { if (durationString != null) { usage(); } i++; long seconds = Long.parseLong(args[i]); durationString = seconds + " seconds"; testDuration = seconds * 1000; } else if (arg.equals("-maxLevel")) { i++; maxLevel = Integer.parseInt(args[i]); } else if (arg.equals("-othervm")) { othervm = true; } else if (arg.equals("-exit")) { exit = true; } else { usage(); } } if (durationString == null) { durationString = testDuration + " milliseconds"; } } catch (Throwable t) { usage(); throw new RuntimeException("TEST FAILED: Bad argument"); } AppleUserImpl user = null; long startTime = 0; Thread server = null; int exitValue = 0; try { user = new AppleUserImpl(); synchronized (user) { // create new registry and bind new AppleUserImpl in registry Registry registry = TestLibrary.createRegistryOnUnusedPort(); registryPort = TestLibrary.getRegistryPort(registry); LocateRegistry.getRegistry(registryPort).rebind("AppleUser", user); // start the other server if applicable if (othervm) { // the other server must be running in a separate process logger.log(Level.INFO, "Application server must be " + "started in separate process"); } else { Class app = Class.forName("ApplicationServer"); java.lang.reflect.Constructor appConstructor = app.getDeclaredConstructor(new Class[] {Integer.TYPE}); server = new Thread((Runnable) appConstructor.newInstance(registryPort)); logger.log(Level.INFO, "Starting application server " + "in same process"); server.start(); } // wait for other server to call startTest method logger.log(Level.INFO, "Waiting for application server " + "process to start"); while (!startTestNotified) { user.wait(); } } startTime = System.currentTimeMillis(); logger.log(Level.INFO, "Test starting"); // wait for exception to be reported or first thread to complete logger.log(Level.INFO, "Waiting " + durationString + " for " + "test to complete or exception to be thrown"); synchronized (lock) { while (status == null && !finished) { lock.wait(); } } if (status != null) { throw new RuntimeException("TEST FAILED: " + "juicer server reported an exception", status); } else { logger.log(Level.INFO, "TEST PASSED"); } } catch (Exception e) { logger.log(Level.INFO, "TEST FAILED"); exitValue = 1; if (exit) { e.printStackTrace(); } throw new RuntimeException("TEST FAILED: " + "unexpected exception", e); } finally { long actualDuration = System.currentTimeMillis() - startTime; logger.log(Level.INFO, "Test finished"); try { UnicastRemoteObject.unexportObject(user, true); } catch (NoSuchObjectException ignore) { } logger.log(Level.INFO, "Test duration was " + (actualDuration/1000) + " seconds " + "(" + (actualDuration/3600000) + " hours)"); System.gc(); System.gc(); if (exit) { System.exit(exitValue); } } } }