blob: a2e1dbec18acfe7f18332074bf296199198bcd33 [file] [log] [blame]
/*
* Copyright (c) 2003, 2015, 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
* @bug 4882798
* @summary multi-thread test to exercise sync and contention for adds to transformer registry
* @author Gabriel Adauto, Wily Technology
*
* @run build TransformerManagementThreadAddTests
* @run shell MakeJAR.sh redefineAgent
* @run main/othervm -javaagent:redefineAgent.jar TransformerManagementThreadAddTests TransformerManagementThreadAddTests
*/
import java.io.*;
import java.lang.instrument.*;
import java.security.ProtectionDomain;
import java.util.*;
public class TransformerManagementThreadAddTests extends ATestCaseScaffold
{
public static void
main (String[] args)
throws Throwable {
ATestCaseScaffold test = new TransformerManagementThreadAddTests(args[0]);
test.runTest();
}
protected void
doRunTest()
throws Throwable {
testMultiThreadAdds();
}
/**
* CONFIGURATION FOR TEST
* ----------------------
* Set these variables to different values to test the object that
* manages the transformers.
*
* MIN_TRANS: the minimum number of transformers to add by a thread
* MAX_TRANS: the maximum number of transformers to add by a thread
* There will be a total of MAX_TRANS-MIN_TRANS+1 threads created.
* Each thread will add between MIN_TRANS and MAX_TRANS transformers
* to the manager.
*
* REMOVE_THREADS: the number of threads to run that spend their time
* removing transformers
*/
protected static final int MIN_TRANS = 33;
protected static final int MAX_TRANS = 45;
protected static final int REMOVE_THREADS = 5;
protected static final boolean LOG_TRANSFORMATIONS = false;
/**
* Field variables
*/
protected static final int TOTAL_THREADS = MAX_TRANS - MIN_TRANS + 1;
private byte[] fDummyClassBytes;
// fCheckedTransformers is a Vector that is used to verify
// that the transform() function is called in the same
// order in which the transformers were added to the
// TransformerManager. The test currently verifies that all
// transformers for a specific worker thread are in
// increasing order by index value.
private Vector fCheckedTransformers;
private Instrumentation fInstrumentation;
private int fFinished;
private ExecuteTransformersThread fExec;
// Need to use this for synchronization in subclass
protected Vector fAddedTransformers;
private String fDummyClassName;
/**
* Constructor for TransformerManagementThreadAddTests.
* @param name Name for the test
*/
public TransformerManagementThreadAddTests(String name)
{
super(name);
fCheckedTransformers = new Vector();
fAddedTransformers = new Vector();
fDummyClassName = "DummyClass";
String resourceName = "DummyClass.class";
File f = new File(System.getProperty("test.classes", "."), resourceName);
System.out.println("Reading test class from " + f);
try
{
InputStream redefineStream = new FileInputStream(f);
fDummyClassBytes = NamedBuffer.loadBufferFromStream(redefineStream);
}
catch (IOException e)
{
fail("Could not load the class: "+resourceName);
}
}
public void
testMultiThreadAdds()
{
TransformerThread[] threads = new TransformerThread[TOTAL_THREADS];
for (int i = MIN_TRANS; i <= MAX_TRANS; i++)
{
int index = i - MIN_TRANS;
threads[index] = new TransformerThread("Trans"+prettyNum(index,2), i);
}
ExecuteTransformersThread exec = new ExecuteTransformersThread();
exec.start();
for (int i = threads.length - 1; i >= 0; i--)
{
threads[i].start();
}
// Effective Java - Item 48: Synchronize access to shared mutable data
// Don't use a direct field getter.
while (!exec.isDone())
{
// Effective Java - Item 51: Don't depend on the thread scheduler
// Use sleep() instead of yield().
try {
Thread.sleep(500);
} catch (InterruptedException ie) {
}
}
assertTrue(finalCheck());
if (LOG_TRANSFORMATIONS) {
printTransformers();
}
}
/**
* Returns the Instrumentation.
* @return Instrumentation the data type with JPLIS calls
*/
public Instrumentation getInstrumentation()
{
return fInstrumentation;
}
/**
* Returns the execution thread
* @return ExecuteTransformersThread
*/
protected ExecuteTransformersThread getExecThread()
{
return fExec;
}
/**
* Sets the execution thread
* @param exec The execution thread to set
*/
protected void setExecThread(ExecuteTransformersThread exec)
{
this.fExec = exec;
}
// Effective Java - Item 48: Synchronize access to shared mutable data
// Document a synchronized setter.
protected synchronized void
threadFinished(Thread t)
{
fFinished++;
}
// Effective Java - Item 48: Synchronize access to shared mutable data
// Provide synchronized getter.
protected synchronized boolean
threadsDone()
{
return fFinished == TOTAL_THREADS;
}
/**
* Method testCompleted.
* @return boolean
*/
protected boolean
testCompleted()
{
// Effective Java - Item 48: Synchronize access to shared mutable data
// Don't use direct field getter.
return getExecThread().isDone();
}
/**
*
*/
protected boolean
finalCheck()
{
if (LOG_TRANSFORMATIONS) {
// log the list
for (int x = 0; x < fCheckedTransformers.size(); x++ ) {
System.out.println(x + "\t\t" + fCheckedTransformers.get(x));
}
System.out.println();
System.out.println();
// check for multiples
for (int x = 0; x < fCheckedTransformers.size(); x++ ) {
Object current = fCheckedTransformers.get(x);
for ( int y = x + 1; y < fCheckedTransformers.size(); y++) {
Object running = fCheckedTransformers.get(y);
if ( current.equals(running) ) {
System.out.println(x + "\t" + y + " \t" + "FOUND DUPLICATE: " + current);
}
}
}
}
for (int j = 1; j < fCheckedTransformers.size(); j++) {
ThreadTransformer transformer = (ThreadTransformer)fCheckedTransformers.get(j);
for (int i = 0; i < j; i++) {
ThreadTransformer currTrans = (ThreadTransformer)fCheckedTransformers.get(i);
assertTrue(currTrans + " incorrectly appeared before " +
transformer + " i=" + i + " j=" + j + " size=" +
fCheckedTransformers.size(),
!(
currTrans.getThread().equals(transformer.getThread()) &&
currTrans.getIndex() > transformer.getIndex()));
}
}
return true;
}
/**
*
*/
protected void
setUp()
throws Exception
{
super.setUp();
fFinished = 0;
assertTrue(MIN_TRANS < MAX_TRANS);
fInstrumentation = InstrumentationHandoff.getInstrumentationOrThrow();
}
/**
*
*/
protected void
tearDown()
throws Exception
{
super.tearDown();
}
/**
* Method executeTransform.
*/
private void
executeTransform()
{
try
{
ClassDefinition cd = new ClassDefinition(DummyClass.class, fDummyClassBytes);
// When the ClassDefinition above is created for the first
// time and every time redefineClasses() below is called,
// the transform() function is called for each registered
// transformer. We only want one complete set of calls to
// be logged in the fCheckedTransformers Vector so we clear
// any calls logged for ClassDefinition above and just use
// the ones logged for redefineClasses() below.
fCheckedTransformers.clear();
getInstrumentation().redefineClasses(new ClassDefinition[]{ cd });
}
catch (ClassNotFoundException e)
{
fail("Could not find the class: "+DummyClass.class.getName());
}
catch (UnmodifiableClassException e)
{
fail("Could not modify the class: "+DummyClass.class.getName());
}
}
/**
* Method addTransformerToManager.
* @param threadTransformer
*/
private void
addTransformerToManager(ThreadTransformer threadTransformer)
{
getInstrumentation().addTransformer(threadTransformer);
fAddedTransformers.add(threadTransformer);
}
/**
* Method checkInTransformer.
* @param myClassFileTransformer
*/
private void
checkInTransformer(ThreadTransformer transformer)
{
fCheckedTransformers.add(transformer);
}
/**
* Method createTransformer.
* @param transformerThread
* @param i
* @return ThreadTransformer
*/
private ThreadTransformer
createTransformer(TransformerThread thread, int index)
{
ThreadTransformer tt = null;
tt = new ThreadTransformer(thread, index);
return tt;
}
protected class
ExecuteTransformersThread
extends Thread
{
private boolean fDone = false;
// Effective Java - Item 48: Synchronize access to shared mutable data
// Provide a synchronized getter.
private synchronized boolean isDone() {
return fDone;
}
// Effective Java - Item 48: Synchronize access to shared mutable data
// Provide a synchronized setter.
private synchronized void setIsDone() {
fDone = true;
}
public void
run()
{
while(!threadsDone())
{
executeTransform();
}
// Do a final check for good measure
executeTransform();
// Effective Java - Item 48: Synchronize access to shared mutable data
// Don't use direct field setter.
setIsDone();
}
}
protected class
TransformerThread
extends Thread
{
private final ThreadTransformer[] fThreadTransformers;
TransformerThread(String name, int numTransformers)
{
super(name);
fThreadTransformers = makeTransformers(numTransformers);
}
/**
* Method makeTransformers.
* @param numTransformers
* @return ThreadTransformer[]
*/
private ThreadTransformer[]
makeTransformers(int numTransformers)
{
ThreadTransformer[] trans = new ThreadTransformer[numTransformers];
for (int i = 0; i < trans.length; i++)
{
trans[i] = createTransformer(TransformerThread.this, i);
}
return trans;
}
public void
run()
{
for (int i = 0; i < fThreadTransformers.length; i++)
{
addTransformerToManager(fThreadTransformers[i]);
}
threadFinished(TransformerThread.this);
}
}
/**
* ClassFileTransformer implementation that knows its thread
*/
protected class
ThreadTransformer extends SimpleIdentityTransformer
{
private final String fName;
private final int fIndex;
private final Thread fThread;
/**
* Constructor for ThreadTransformer.
*/
public ThreadTransformer(Thread thread, int index) {
super();
fThread = thread;
fIndex = index;
fName = "TT["+fThread.getName()+"]["+prettyNum(fIndex,3)+"]";
}
public String toString()
{
return fName;
}
/**
*
*/
public byte[]
transform(
ClassLoader loader,
String className,
Class<?> classBeingRedefined,
ProtectionDomain domain,
byte[] classfileBuffer)
{
if ( className.equals(TransformerManagementThreadAddTests.this.fDummyClassName) ) {
checkInTransformer(ThreadTransformer.this);
}
return super.transform( loader,
className,
classBeingRedefined,
domain,
classfileBuffer);
}
/**
* Returns the index.
* @return int
*/
public int getIndex()
{
return fIndex;
}
/**
* Returns the thread.
* @return Thread
*/
public Thread getThread()
{
return fThread;
}
}
/**
* DEBUG STUFF
*/
private int NUM_SWITCHES;
/**
* Method printTransformers.
*/
protected void printTransformers()
{
NUM_SWITCHES = 0;
Iterator trans = fCheckedTransformers.iterator();
ThreadTransformer old = null;
StringBuffer buf = new StringBuffer();
for (int i = 1; trans.hasNext(); i++)
{
ThreadTransformer t = (ThreadTransformer)trans.next();
buf.append(t.toString());
if (old != null)
{
if (!old.getThread().equals(t.getThread()))
{
NUM_SWITCHES++;
buf.append("*");
}
else
{ buf.append(" "); }
}
else
{ buf.append(" "); }
if (i % 5 == 0)
{
buf.append("\n");
}
else
{ buf.append(" "); }
old = t;
}
System.out.println(buf);
System.out.println("\nNumber of transitions from one thread to another: "+NUM_SWITCHES);
}
protected String
prettyNum(int n, int numSize)
{
StringBuffer num = new StringBuffer(Integer.toString(n));
int size = num.length();
for (int i = 0; i < numSize - size; i++)
{
num.insert(0, "0");
}
return num.toString();
}
/**
* END DEBUG STUFF
*/
}