1 /* 2 * Copyright (c) 2003, 2016, 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 * @modules java.rmi/sun.rmi.registry 59 * java.rmi/sun.rmi.server 60 * java.rmi/sun.rmi.transport 61 * java.rmi/sun.rmi.transport.tcp 62 * @build TestLibrary 63 * Apple AppleEvent AppleImpl 64 * Orange OrangeEcho OrangeEchoImpl OrangeImpl 65 * ApplicationServer 66 * 67 * @run main/othervm/policy=security.policy AppleUserImpl -seconds 30 68 * 69 * @author Peter Jones, Nigel Daley 70 */ 71 72 import java.rmi.NoSuchObjectException; 73 import java.rmi.RemoteException; 74 import java.rmi.registry.LocateRegistry; 75 import java.rmi.registry.Registry; 76 import java.rmi.server.UnicastRemoteObject; 77 import java.util.Random; 78 import java.util.logging.Level; 79 import java.util.logging.Logger; 80 81 /** 82 * The AppleUserImpl class implements the behavior of the remote 83 * "apple user" objects exported by the server. The application server 84 * passes each of its remote "apple" objects to an apple user, and an 85 * AppleUserThread is created for each apple. 86 */ 87 public class AppleUserImpl extends UnicastRemoteObject implements AppleUser { 88 private static int registryPort = -1; 89 private static final Logger logger = 90 Logger.getLogger("reliability.appleuser"); 91 private static int threadNum = 0; 92 private static long testDuration = 0; 93 private static int maxLevel = 7; 94 private static Exception status = null; 95 private static boolean finished = false; 96 private static boolean startTestNotified = false; 97 private static final Random random = new Random(); 98 private static final Object lock = new Object(); 99 AppleUserImpl()100 public AppleUserImpl() throws RemoteException { 101 } 102 103 /** 104 * Allows the other server process to indicate that it is ready 105 * to start "juicing". 106 */ startTest()107 public synchronized void startTest() throws RemoteException { 108 startTestNotified = true; 109 this.notifyAll(); 110 } 111 112 /** 113 * Allows the other server process to report an exception to this 114 * process and thereby terminate the test. 115 */ reportException(Exception status)116 public void reportException(Exception status) throws RemoteException { 117 synchronized (lock) { 118 this.status = status; 119 lock.notifyAll(); 120 } 121 } 122 123 /** 124 * "Use" supplied apple object. Create an AppleUserThread to 125 * stress it out. 126 */ useApple(Apple apple)127 public synchronized void useApple(Apple apple) throws RemoteException { 128 String threadName = Thread.currentThread().getName(); 129 logger.log(Level.FINEST, 130 threadName + ": AppleUserImpl.useApple(): BEGIN"); 131 132 AppleUserThread t = 133 new AppleUserThread("AppleUserThread-" + (++threadNum), apple); 134 t.start(); 135 136 logger.log(Level.FINEST, 137 threadName + ": AppleUserImpl.useApple(): END"); 138 } 139 140 /** 141 * The AppleUserThread class repeatedly invokes calls on its associated 142 * Apple object to stress the RMI system. 143 */ 144 class AppleUserThread extends Thread { 145 146 final Apple apple; 147 AppleUserThread(String name, Apple apple)148 public AppleUserThread(String name, Apple apple) { 149 super(name); 150 this.apple = apple; 151 } 152 run()153 public void run() { 154 int orangeNum = 0; 155 long stopTime = System.currentTimeMillis() + testDuration; 156 Logger logger = Logger.getLogger("reliability.appleuserthread"); 157 158 try { 159 do { // loop until stopTime is reached 160 161 /* 162 * Notify apple with some apple events. This tests 163 * serialization of arrays. 164 */ 165 int numEvents = Math.abs(random.nextInt() % 5); 166 AppleEvent[] events = new AppleEvent[numEvents]; 167 for (int i = 0; i < events.length; i++) { 168 events[i] = new AppleEvent(orangeNum % 3); 169 } 170 apple.notify(events); 171 172 /* 173 * Request a new orange object be created in 174 * the application server. 175 */ 176 Orange orange = apple.newOrange( 177 "Orange(" + getName() + ")-" + (++orangeNum)); 178 179 /* 180 * Create a large message of random ints to pass to orange. 181 */ 182 int msgLength = 1000 + Math.abs(random.nextInt() % 3000); 183 int[] message = new int[msgLength]; 184 for (int i = 0; i < message.length; i++) { 185 message[i] = random.nextInt(); 186 } 187 188 /* 189 * Invoke recursive call on the orange. Base case 190 * of recursion inverts messgage. 191 */ 192 OrangeEchoImpl echo = new OrangeEchoImpl( 193 "OrangeEcho(" + getName() + ")-" + orangeNum); 194 int[] response = orange.recurse(echo, message, 195 2 + Math.abs(random.nextInt() % (maxLevel + 1))); 196 197 /* 198 * Verify message was properly inverted and not corrupted 199 * through all the recursive method invocations. 200 */ 201 if (response.length != message.length) { 202 throw new RuntimeException( 203 "ERROR: CORRUPTED RESPONSE: " + 204 "wrong length of returned array " + "(should be " + 205 message.length + ", is " + response.length + ")"); 206 } 207 for (int i = 0; i < message.length; i++) { 208 if (~message[i] != response[i]) { 209 throw new RuntimeException( 210 "ERROR: CORRUPTED RESPONSE: " + 211 "at element " + i + "/" + message.length + 212 " of returned array (should be " + 213 Integer.toHexString(~message[i]) + ", is " + 214 Integer.toHexString(response[i]) + ")"); 215 } 216 } 217 218 try { 219 Thread.sleep(Math.abs(random.nextInt() % 10) * 1000); 220 } catch (InterruptedException e) { 221 } 222 223 } while (System.currentTimeMillis() < stopTime); 224 225 } catch (Exception e) { 226 status = e; 227 } 228 finished = true; 229 synchronized (lock) { 230 lock.notifyAll(); 231 } 232 } 233 } 234 usage()235 private static void usage() { 236 System.err.println("Usage: AppleUserImpl [-hours <hours> | " + 237 "-seconds <seconds>]"); 238 System.err.println(" [-maxLevel <maxLevel>]"); 239 System.err.println(" [-othervm]"); 240 System.err.println(" [-exit]"); 241 System.err.println(" hours The number of hours to run the juicer."); 242 System.err.println(" The default is 0 hours."); 243 System.err.println(" seconds The number of seconds to run the juicer."); 244 System.err.println(" The default is 0 seconds."); 245 System.err.println(" maxLevel The maximum number of levels to "); 246 System.err.println(" recurse on each call."); 247 System.err.println(" The default is 7 levels."); 248 System.err.println(" othervm If present, the VM will wait for the"); 249 System.err.println(" ApplicationServer to start in"); 250 System.err.println(" another process."); 251 System.err.println(" The default is to run everything in"); 252 System.err.println(" a single VM."); 253 System.err.println(" exit If present, the VM will call"); 254 System.err.println(" System.exit() when main() finishes."); 255 System.err.println(" The default is to not call"); 256 System.err.println(" System.exit()."); 257 System.err.println(); 258 } 259 260 /** 261 * Entry point for the "juicer" server process. Create and export 262 * an apple user implementation in an rmiregistry running on localhost. 263 */ main(String[] args)264 public static void main(String[] args) { 265 String durationString = null; 266 boolean othervm = false; 267 boolean exit = false; 268 try { 269 // parse command line args 270 for (int i = 0; i < args.length ; i++ ) { 271 String arg = args[i]; 272 if (arg.equals("-hours")) { 273 if (durationString != null) { 274 usage(); 275 } 276 i++; 277 int hours = Integer.parseInt(args[i]); 278 durationString = hours + " hours"; 279 testDuration = hours * 60 * 60 * 1000; 280 } else if (arg.equals("-seconds")) { 281 if (durationString != null) { 282 usage(); 283 } 284 i++; 285 long seconds = Long.parseLong(args[i]); 286 durationString = seconds + " seconds"; 287 testDuration = seconds * 1000; 288 } else if (arg.equals("-maxLevel")) { 289 i++; 290 maxLevel = Integer.parseInt(args[i]); 291 } else if (arg.equals("-othervm")) { 292 othervm = true; 293 } else if (arg.equals("-exit")) { 294 exit = true; 295 } else { 296 usage(); 297 } 298 } 299 if (durationString == null) { 300 durationString = testDuration + " milliseconds"; 301 } 302 } catch (Throwable t) { 303 usage(); 304 throw new RuntimeException("TEST FAILED: Bad argument"); 305 } 306 307 AppleUserImpl user = null; 308 long startTime = 0; 309 Thread server = null; 310 int exitValue = 0; 311 try { 312 user = new AppleUserImpl(); 313 314 synchronized (user) { 315 // create new registry and bind new AppleUserImpl in registry 316 Registry registry = TestLibrary.createRegistryOnEphemeralPort(); 317 registryPort = TestLibrary.getRegistryPort(registry); 318 LocateRegistry.getRegistry(registryPort).rebind("AppleUser", 319 user); 320 321 // start the other server if applicable 322 if (othervm) { 323 // the other server must be running in a separate process 324 logger.log(Level.INFO, "Application server must be " + 325 "started in separate process"); 326 } else { 327 Class app = Class.forName("ApplicationServer"); 328 java.lang.reflect.Constructor appConstructor = 329 app.getDeclaredConstructor(new Class[] {Integer.TYPE}); 330 server = new Thread((Runnable) appConstructor.newInstance(registryPort)); 331 logger.log(Level.INFO, "Starting application server " + 332 "in same process"); 333 server.start(); 334 } 335 336 // wait for other server to call startTest method 337 logger.log(Level.INFO, "Waiting for application server " + 338 "process to start"); 339 while (!startTestNotified) { 340 user.wait(); 341 } 342 } 343 344 startTime = System.currentTimeMillis(); 345 logger.log(Level.INFO, "Test starting"); 346 347 // wait for exception to be reported or first thread to complete 348 logger.log(Level.INFO, "Waiting " + durationString + " for " + 349 "test to complete or exception to be thrown"); 350 351 synchronized (lock) { 352 while (status == null && !finished) { 353 lock.wait(); 354 } 355 } 356 357 if (status != null) { 358 throw new RuntimeException("TEST FAILED: " 359 + "juicer server reported an exception", status); 360 } else { 361 logger.log(Level.INFO, "TEST PASSED"); 362 } 363 } catch (Exception e) { 364 logger.log(Level.INFO, "TEST FAILED"); 365 exitValue = 1; 366 if (exit) { 367 e.printStackTrace(); 368 } 369 throw new RuntimeException("TEST FAILED: " 370 + "unexpected exception", e); 371 } finally { 372 long actualDuration = System.currentTimeMillis() - startTime; 373 logger.log(Level.INFO, "Test finished"); 374 try { 375 UnicastRemoteObject.unexportObject(user, true); 376 } catch (NoSuchObjectException ignore) { 377 } 378 logger.log(Level.INFO, "Test duration was " + 379 (actualDuration/1000) + " seconds " + 380 "(" + (actualDuration/3600000) + " hours)"); 381 System.gc(); System.gc(); 382 if (exit) { 383 System.exit(exitValue); 384 } 385 } 386 } 387 } 388