/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* @test 1.6 03/10/28
*
* @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
* (thus in JERI, dgc must be enabled for this test to function).
* 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 run() 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. The default is to start the server in
* the same VM. If the test is being run in distributed mode, then
* the server will be started in a separate VM on a different host.
*
* 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 defined by the property
* com.sun.jini.test.impl.reliabililty.maxLevel. 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.
*
* Test properties are:
*
* com.sun.jini.test.impl.reliabililty.minutes
* The number of minutes to run the juicer.
* The default is 1 minute.
* com.sun.jini.test.impl.reliabililty.maxLevel
* The maximum number of levels to
* recurse on each call.
* The default is 7 levels.
*
* @author Peter Jones, Nigel Daley
*/
package com.sun.jini.test.impl.reliability;
import java.rmi.RemoteException;
import java.rmi.NoSuchObjectException;
import java.rmi.registry.LocateRegistry;
import java.util.Random;
import java.util.logging.Level;
import java.net.InetAddress;
import net.jini.config.Configuration;
import net.jini.export.Exporter;
import com.sun.jini.qa.harness.QATest;
import com.sun.jini.qa.harness.QAConfig;
import com.sun.jini.qa.harness.SlaveRequest;
import com.sun.jini.qa.harness.SlaveTest;
/**
* 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 QATest implements AppleUser {
private int threadNum = 0;
private long testDuration;
private int maxLevel;
private Exception status = null;
private boolean finished = false;
private boolean startTestNotified = false;
private final Random random = new Random();
private final Object lock = new Object();
private long startTime = 0;
private Exporter exporter;
/**
* 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;
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.
*/
OrangeEcho orangeEchoProxy = (
new OrangeEchoImpl(
"OrangeEcho(" + getName() + ")-" + orangeNum)
).export();
int[] response = orange.recurse(orangeEchoProxy, 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();
}
}
}
// inherit javadoc
public void setup(QAConfig sysConfig) throws Exception {
super.setup(sysConfig);
}
/**
* Entry point for the "juicer" server process. Create and export
* an apple user implementation in an rmiregistry running on localhost.
*/
public void run() throws Exception {
// run ApplicationServer on a separate host if running distributed
boolean othervm = (QAConfig.getConfig().getHostList().size() > 1);
maxLevel = config.getIntConfigVal(
"com.sun.jini.test.impl.reliability.maxLevel",7);
testDuration = config.getIntConfigVal(
"com.sun.jini.test.impl.reliability.minutes",1) * 60 * 1000;
Configuration c = QAConfig.getConfig().getConfiguration();
exporter = (Exporter) c.getEntry("test",
"reliabilityExporter",
Exporter.class);
AppleUser appleUserProxy = (AppleUser) exporter.export(this);
Thread server = null;
synchronized (this) {
// create new registry and bind new AppleUserImpl in registry
LocateRegistry.createRegistry(2006);
LocateRegistry.getRegistry(2006).rebind(
"AppleUser",appleUserProxy);
// start the other server if applicable
if (othervm) {
// the other server must be running in a separate process
logger.log(Level.INFO, "Application server will be " +
"started on a separate host");
SlaveRequest sr = new StartApplicationServerRequest(
InetAddress.getLocalHost().getHostName());
SlaveTest.broadcast(sr);
} else {
Class app = Class.forName(
"com.sun.jini.test.impl.reliability.ApplicationServer");
server = new Thread((Runnable) app.newInstance());
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) {
this.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 " + (testDuration/60000) +
" minutes 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");
}
}
// inherit javadoc
public void tearDown() {
long actualDuration = System.currentTimeMillis() - startTime;
logger.log(Level.INFO, "Test finished");
exporter.unexport(true);
logger.log(Level.INFO, "Test duration was " +
(actualDuration/1000) + " seconds " +
"(" + (actualDuration/60000) + " minutes or " +
(actualDuration/3600000) + " hours)");
super.tearDown();
}
}