/******************************************************************************* * Copyright (c) 2012 Contributors. * All rights reserved. * This program and the accompanying materials are made available * under the terms of the Eclipse Public License v1.0 * which accompanies this distribution and is available at * http://eclipse.org/legal/epl-v10.html * * Contributors: * Lyor Goldstein (vmware) fix WeavingAdaptor re-entrancy issue *******************************************************************************/ package org.aspectj.weaver.tools; import java.util.Arrays; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.aspectj.weaver.tools.Bug400649WeavingAdaptor.ByteTransformer; /** * @author Lyor Goldstein * @since Feb 21, 2013 2013 */ public class Bug400649WeavingAdaptorTest extends TestCase { public Bug400649WeavingAdaptorTest() { super(); } public Bug400649WeavingAdaptorTest(String name) { super(name); } public void testOldReentrancyProtectionMechanism() { testReentrancyProtection(true); } public void testNewReentrancyProtectionMechanism() { testReentrancyProtection(false); } private void testReentrancyProtection(final boolean useOldMechanism) { final Semaphore blockingSem=new Semaphore(0, true); final Semaphore signalSem=new Semaphore(0, true); final String BLOCK_NAME="blockMe[" + useOldMechanism + "]"; final ByteTransformer testTransformer=new ByteTransformer() { public byte[] transform(String name, byte[] bytes) throws Exception { Thread t=Thread.currentThread(); if (BLOCK_NAME.equals(name)) { signalSem.release(); // signal the other thread that it is OK to start running System.out.println(t.getName() + ": Signalled release - blocking"); // block "forever" to ensure that the other thread also enters "weaveClass" if (!blockingSem.tryAcquire(15, TimeUnit.SECONDS)) { throw new IllegalStateException("Blocking not released on time"); } System.out.println(t.getName() + ": Continuing to weave"); } return name.getBytes(); } }; final Bug400649WeavingAdaptor adaptor=new Bug400649WeavingAdaptor(testTransformer, useOldMechanism); final AtomicReference blockingException=new AtomicReference(null); // this is the code for the thread that blocks to ensure concurrent access to the WeavingAdaptor#weaveClass final Thread blockingThread=new Thread(new Runnable() { public void run() { Thread t=Thread.currentThread(); System.out.println(t.getName() + ": Started"); try { assertFalse("Weaver unexpectedly marked as running", adaptor.isRunning()); byte[] expected=BLOCK_NAME.getBytes(); byte[] actual=adaptor.weaveClass(BLOCK_NAME, new byte[] { 3, 7, 7, 7, 3, 4, 7 }); System.out.println(t.getName() + ": Checking transformed results"); assertEquals("Mismatched transformed length", expected.length, actual.length); for (int index=0; index < expected.length; index++) { final byte expByte=expected[index], actByte=actual[index]; if (expByte != actByte) { fail("Mismatched value at index=" + index + " - " + actByte + " instead of " + expByte + ": expected=" + Arrays.toString(expected) + ", actual=" + Arrays.toString(actual)); } } } catch(Throwable e) { System.err.println(t.getName() + ": " + e.getClass().getSimpleName() + " - " + e.getMessage()); blockingException.set(e); } System.out.println(t.getName() + ": Exiting"); } }, "tBlockedThread[" + useOldMechanism + "]"); blockingThread.start(); // this is the code for the thread that concurrently enters weaveClass while the blocking thread is already executing it final AtomicReference concurrentException=new AtomicReference(null); final String CONCURRENT_FAILURE_TEXT="Unexpected transformed bytes"; final Thread concurrentThread=new Thread(new Runnable() { public void run() { Thread t=Thread.currentThread(); System.out.println(t.getName() + ": Started"); try { final String TEST_NAME="testReentrancyProtection[" + useOldMechanism + "]"; final byte[] expected={ 7, 3, 1, 9, 6, 5 }; if (!signalSem.tryAcquire(10, TimeUnit.SECONDS)) { throw new IllegalStateException("No start signal received"); } System.out.println(t.getName() + ": Released from wait - weaving"); if (useOldMechanism) { /* * THIS IS THE ESSENCE OF THE BUG !!! Since the old * mechanism uses a ThreadLocal, this thread thinks it * is the only one running whereas it really isn't... */ assertFalse("Weaver unexpectedly marked as running", adaptor.isRunning()); } else { assertTrue("Weaver not marked as running", adaptor.isRunning()); } final byte[] actual=adaptor.weaveClass(TEST_NAME, expected); System.out.println(t.getName() + ": Releasing blocked thread"); blockingSem.release(); // release the blocking thread // we expect the same bytes since the adaptor is supposed to have "weaverRunning == true" assertSame(CONCURRENT_FAILURE_TEXT, expected, actual); } catch(Throwable e) { System.err.println(t.getName() + ": " + e.getClass().getSimpleName() + " - " + e.getMessage()); concurrentException.set(e); } System.out.println(t.getName() + ": Exiting"); } }, "tConcurrentThread[" + useOldMechanism + "]"); concurrentThread.start(); for (Thread t : new Thread[] { concurrentThread, blockingThread }) { System.out.println("Waiting for " + t.getName()); try { t.join(TimeUnit.SECONDS.toMillis(25L)); } catch(InterruptedException e) { System.err.println("Interrupted while waiting for " + t.getName() + ": " + e.getMessage()); } assertFalse(t.getName() + " still alive", t.isAlive()); } Throwable t=blockingException.get(); if (t != null) { fail("Unexpected blocking thread exception - " + t.getClass().getSimpleName() + ": " + t.getMessage()); } t = concurrentException.get(); if (useOldMechanism) { if (t == null) { fail("Unexpected concurrent thread success"); } if (!(t instanceof AssertionFailedError)) { fail("Unexpected concurrent thread failure type - " + t.getClass().getSimpleName() + ": " + t.getMessage()); } AssertionFailedError err=(AssertionFailedError) t; String reason=err.getMessage(); // make sure the reason for the failure is the essence of the bug if (!reason.startsWith(CONCURRENT_FAILURE_TEXT)) { fail("Mismatched concurrent thread failure reason: " + reason); } } else { if (t != null) { fail("Unexpected concurrent thread exception - " + t.getClass().getSimpleName() + ": " + t.getMessage()); } } } }