1 /* 2 * Copyright (c) 2003, 2012, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 /* @test 25 * 26 * @summary The juicer is the classic RMI stress test. The juicer makes 27 * a large number of concurrent, long running, remote method invocations 28 * between many threads which have exported remote objects. These 29 * threads use remote objects that carry on deep "two party" 30 * recursion. The juicer relies on Distributed Garbage Collection to 31 * unexport these remote objects when no more references are held to them. 32 * The two parties in the recursion are OrangeImpl and 33 * OrangeEchoImpl. OrangeImpl checks the base case of the recursion 34 * so that the program will exit. 35 * 36 * When the AppleUserImpl.main() method is invoked, the class binds an 37 * instance of itself in a registry. A second server process, 38 * an ApplicationServer, is started which looks up the recently 39 * bound AppleUser object. This server is either started up in 40 * the same VM or can optionally be started in a separate VM on the 41 * same host or on a different host. When this test is run on the 42 * RMI profile, ApplicationServer must be started by AppleUserImpl 43 * and the complete juicer runs in a single process. 44 * 45 * The second server process instructs the AppleUserImpl to "use" some apples. 46 * AppleUserImpl creates a new thread for each apple. These threads 47 * initiate the two party recursion. 48 * 49 * Each recursive call nests to a depth determined by this 50 * expression: (2 + Math.abs(random.nextInt() % (maxLevel + 1)), 51 * where maxLevel is a command line parameter. Thus each recursive 52 * call nests a random number of levels between 2 and maxLevel. 53 * 54 * The test ends when an exception is encountered or the stop time 55 * has been reached. 56 * 57 * @library ../../testlibrary 58 * 59 * @build Apple AppleEvent AppleImpl AppleUserImpl 60 * @build Orange OrangeEcho OrangeEchoImpl OrangeImpl 61 * @build ApplicationServer 62 * @build TestLibrary 63 * 64 * @run main/othervm/policy=security.policy AppleUserImpl -seconds 30 65 * 66 * @author Peter Jones, Nigel Daley 67 */ 68 69 import java.rmi.NoSuchObjectException; 70 import java.rmi.RemoteException; 71 import java.rmi.registry.LocateRegistry; 72 import java.rmi.registry.Registry; 73 import java.rmi.server.UnicastRemoteObject; 74 import java.util.Random; 75 import java.util.logging.Level; 76 import java.util.logging.Logger; 77 78 /** 79 * The AppleUserImpl class implements the behavior of the remote 80 * "apple user" objects exported by the server. The application server 81 * passes each of its remote "apple" objects to an apple user, and an 82 * AppleUserThread is created for each apple. 83 */ 84 public class AppleUserImpl extends UnicastRemoteObject implements AppleUser { 85 private static int registryPort = -1; 86 private static final Logger logger = 87 Logger.getLogger("reliability.appleuser"); 88 private static int threadNum = 0; 89 private static long testDuration = 0; 90 private static int maxLevel = 7; 91 private static Exception status = null; 92 private static boolean finished = false; 93 private static boolean startTestNotified = false; 94 private static final Random random = new Random(); 95 private static final Object lock = new Object(); 96 97 public AppleUserImpl() throws RemoteException { 98 } 99 100 /** 101 * Allows the other server process to indicate that it is ready 102 * to start "juicing". 103 */ 104 public synchronized void startTest() throws RemoteException { 105 startTestNotified = true; 106 this.notifyAll(); 107 } 108 109 /** 110 * Allows the other server process to report an exception to this 111 * process and thereby terminate the test. 112 */ 113 public void reportException(Exception status) throws RemoteException { 114 synchronized (lock) { 115 this.status = status; 116 lock.notifyAll(); 117 } 118 } 119 120 /** 121 * "Use" supplied apple object. Create an AppleUserThread to 122 * stress it out. 123 */ 124 public synchronized void useApple(Apple apple) throws RemoteException { 125 String threadName = Thread.currentThread().getName(); 126 logger.log(Level.FINEST, 127 threadName + ": AppleUserImpl.useApple(): BEGIN"); 128 129 AppleUserThread t = 130 new AppleUserThread("AppleUserThread-" + (++threadNum), apple); 131 t.start(); 132 133 logger.log(Level.FINEST, 134 threadName + ": AppleUserImpl.useApple(): END"); 135 } 136 137 /** 138 * The AppleUserThread class repeatedly invokes calls on its associated 139 * Apple object to stress the RMI system. 140 */ 141 class AppleUserThread extends Thread { 142 143 final Apple apple; 144 145 public AppleUserThread(String name, Apple apple) { 146 super(name); 147 this.apple = apple; 148 } 149 150 public void run() { 151 int orangeNum = 0; 152 long stopTime = System.currentTimeMillis() + testDuration; 153 Logger logger = Logger.getLogger("reliability.appleuserthread"); 154 155 try { 156 do { // loop until stopTime is reached 157 158 /* 159 * Notify apple with some apple events. This tests 160 * serialization of arrays. 161 */ 162 int numEvents = Math.abs(random.nextInt() % 5); 163 AppleEvent[] events = new AppleEvent[numEvents]; 164 for (int i = 0; i < events.length; i++) { 165 events[i] = new AppleEvent(orangeNum % 3); 166 } 167 apple.notify(events); 168 169 /* 170 * Request a new orange object be created in 171 * the application server. 172 */ 173 Orange orange = apple.newOrange( 174 "Orange(" + getName() + ")-" + (++orangeNum)); 175 176 /* 177 * Create a large message of random ints to pass to orange. 178 */ 179 int msgLength = 1000 + Math.abs(random.nextInt() % 3000); 180 int[] message = new int[msgLength]; 181 for (int i = 0; i < message.length; i++) { 182 message[i] = random.nextInt(); 183 } 184 185 /* 186 * Invoke recursive call on the orange. Base case 187 * of recursion inverts messgage. 188 */ 189 OrangeEchoImpl echo = new OrangeEchoImpl( 190 "OrangeEcho(" + getName() + ")-" + orangeNum); 191 int[] response = orange.recurse(echo, message, 192 2 + Math.abs(random.nextInt() % (maxLevel + 1))); 193 194 /* 195 * Verify message was properly inverted and not corrupted 196 * through all the recursive method invocations. 197 */ 198 if (response.length != message.length) { 199 throw new RuntimeException( 200 "ERROR: CORRUPTED RESPONSE: " + 201 "wrong length of returned array " + "(should be " + 202 message.length + ", is " + response.length + ")"); 203 } 204 for (int i = 0; i < message.length; i++) { 205 if (~message[i] != response[i]) { 206 throw new RuntimeException( 207 "ERROR: CORRUPTED RESPONSE: " + 208 "at element " + i + "/" + message.length + 209 " of returned array (should be " + 210 Integer.toHexString(~message[i]) + ", is " + 211 Integer.toHexString(response[i]) + ")"); 212 } 213 } 214 215 try { 216 Thread.sleep(Math.abs(random.nextInt() % 10) * 1000); 217 } catch (InterruptedException e) { 218 } 219 220 } while (System.currentTimeMillis() < stopTime); 221 222 } catch (Exception e) { 223 status = e; 224 } 225 finished = true; 226 synchronized (lock) { 227 lock.notifyAll(); 228 } 229 } 230 } 231 232 private static void usage() { 233 System.err.println("Usage: AppleUserImpl [-hours <hours> | " + 234 "-seconds <seconds>]"); 235 System.err.println(" [-maxLevel <maxLevel>]"); 236 System.err.println(" [-othervm]"); 237 System.err.println(" [-exit]"); 238 System.err.println(" hours The number of hours to run the juicer."); 239 System.err.println(" The default is 0 hours."); 240 System.err.println(" seconds The number of seconds to run the juicer."); 241 System.err.println(" The default is 0 seconds."); 242 System.err.println(" maxLevel The maximum number of levels to "); 243 System.err.println(" recurse on each call."); 244 System.err.println(" The default is 7 levels."); 245 System.err.println(" othervm If present, the VM will wait for the"); 246 System.err.println(" ApplicationServer to start in"); 247 System.err.println(" another process."); 248 System.err.println(" The default is to run everything in"); 249 System.err.println(" a single VM."); 250 System.err.println(" exit If present, the VM will call"); 251 System.err.println(" System.exit() when main() finishes."); 252 System.err.println(" The default is to not call"); 253 System.err.println(" System.exit()."); 254 System.err.println(); 255 } 256 257 /** 258 * Entry point for the "juicer" server process. Create and export 259 * an apple user implementation in an rmiregistry running on localhost. 260 */ 261 public static void main(String[] args) { 262 String durationString = null; 263 boolean othervm = false; 264 boolean exit = false; 265 try { 266 // parse command line args 267 for (int i = 0; i < args.length ; i++ ) { 268 String arg = args[i]; 269 if (arg.equals("-hours")) { 270 if (durationString != null) { 271 usage(); 272 } 273 i++; 274 int hours = Integer.parseInt(args[i]); 275 durationString = hours + " hours"; 276 testDuration = hours * 60 * 60 * 1000; 277 } else if (arg.equals("-seconds")) { 278 if (durationString != null) { 279 usage(); 280 } 281 i++; 282 long seconds = Long.parseLong(args[i]); 283 durationString = seconds + " seconds"; 284 testDuration = seconds * 1000; 285 } else if (arg.equals("-maxLevel")) { 286 i++; 287 maxLevel = Integer.parseInt(args[i]); 288 } else if (arg.equals("-othervm")) { 289 othervm = true; 290 } else if (arg.equals("-exit")) { 291 exit = true; 292 } else { 293 usage(); 294 } 295 } 296 if (durationString == null) { 297 durationString = testDuration + " milliseconds"; 298 } 299 } catch (Throwable t) { 300 usage(); 301 throw new RuntimeException("TEST FAILED: Bad argument"); 302 } 303 304 AppleUserImpl user = null; 305 long startTime = 0; 306 Thread server = null; 307 int exitValue = 0; 308 try { 309 user = new AppleUserImpl(); 310 311 synchronized (user) { 312 // create new registry and bind new AppleUserImpl in registry 313 Registry registry = TestLibrary.createRegistryOnUnusedPort(); 314 registryPort = TestLibrary.getRegistryPort(registry); 315 LocateRegistry.getRegistry(registryPort).rebind("AppleUser", 316 user); 317 318 // start the other server if applicable 319 if (othervm) { 320 // the other server must be running in a separate process 321 logger.log(Level.INFO, "Application server must be " + 322 "started in separate process"); 323 } else { 324 Class app = Class.forName("ApplicationServer"); 325 java.lang.reflect.Constructor appConstructor = 326 app.getDeclaredConstructor(new Class[] {Integer.TYPE}); 327 server = new Thread((Runnable) appConstructor.newInstance(registryPort)); 328 logger.log(Level.INFO, "Starting application server " + 329 "in same process"); 330 server.start(); 331 } 332 333 // wait for other server to call startTest method 334 logger.log(Level.INFO, "Waiting for application server " + 335 "process to start"); 336 while (!startTestNotified) { 337 user.wait(); 338 } 339 } 340 341 startTime = System.currentTimeMillis(); 342 logger.log(Level.INFO, "Test starting"); 343 344 // wait for exception to be reported or first thread to complete 345 logger.log(Level.INFO, "Waiting " + durationString + " for " + 346 "test to complete or exception to be thrown"); 347 348 synchronized (lock) { 349 while (status == null && !finished) { 350 lock.wait(); 351 } 352 } 353 354 if (status != null) { 355 throw new RuntimeException("TEST FAILED: " 356 + "juicer server reported an exception", status); 357 } else { 358 logger.log(Level.INFO, "TEST PASSED"); 359 } 360 } catch (Exception e) { 361 logger.log(Level.INFO, "TEST FAILED"); 362 exitValue = 1; 363 if (exit) { 364 e.printStackTrace(); 365 } 366 throw new RuntimeException("TEST FAILED: " 367 + "unexpected exception", e); 368 } finally { 369 long actualDuration = System.currentTimeMillis() - startTime; 370 logger.log(Level.INFO, "Test finished"); 371 try { 372 UnicastRemoteObject.unexportObject(user, true); 373 } catch (NoSuchObjectException ignore) { 374 } 375 logger.log(Level.INFO, "Test duration was " + 376 (actualDuration/1000) + " seconds " + 377 "(" + (actualDuration/3600000) + " hours)"); 378 System.gc(); System.gc(); 379 if (exit) { 380 System.exit(exitValue); 381 } 382 } 383 } 384 }