| /* | |
| * 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 | |
| */ | |
| } |