/* * JBoss, Home of Professional Open Source * Copyright 2005, JBoss Inc., and individual contributors as indicated * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.as.ejb3.concurrency; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import javax.ejb.ConcurrentAccessTimeoutException; import javax.ejb.IllegalLoopbackException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; /** * Tests the {@link EJBReadWriteLock} * * @author Jaikiran Pai * @version $Revision: $ */ public class EJBReadWriteLockTest { /** * Used in tests */ private EJBReadWriteLock ejbReadWriteLock; @Before public void beforeTest() { this.ejbReadWriteLock = new EJBReadWriteLock(); } @After public void afterTest() { this.ejbReadWriteLock = null; } /** * Test that a {@link javax.ejb.IllegalLoopbackException} is thrown when the thread owning a read lock * tries to obtain a write lock * * @throws Exception */ @Test public void testIllegalLoopBack() throws Exception { // get a read lock Lock readLock = this.ejbReadWriteLock.readLock(); // lock it! readLock.lock(); // now get a write lock and try to lock it (should fail with IllegalLoopBack) Lock writeLock = this.ejbReadWriteLock.writeLock(); try { writeLock.lock(); // unlock the (unexpected obtained lock) and then fail the testcase writeLock.unlock(); Assert.fail("Unexpected acquired write lock"); } catch (IllegalLoopbackException ilbe) { // expected } finally { // unlock the write lock readLock.unlock(); } } /** * Test that when a thread tries to obtain a read lock when another thread holds a write lock, * fails to acquire the lock, if the write lock is not released within the timeout specified * * @throws Exception */ @Test public void testTimeout() throws Exception { // we use a countdown latch for the 2 threads involved CountDownLatch latch = new CountDownLatch(2); // get a write lock Lock writeLock = this.ejbReadWriteLock.writeLock(); // create a thread which will get hold of a write lock // and do some processing for 5 seconds Thread threadHoldingWriteLock = new Thread(new ThreadHoldingWriteLock(latch, writeLock, 5000)); // get a read lock Lock readLock = this.ejbReadWriteLock.readLock(); // start the write lock thread (which internally will obtain // a write lock and start a 5 second processing) threadHoldingWriteLock.start(); // wait for few milli sec for the write lock thread to obtain a write lock Thread.sleep(500); // now try and get a read lock, *shouldn't* be able to obtain the lock // before the 2 second timeout try { // try a read lock with 2 second timeout boolean readLockAcquired = readLock.tryLock(2, TimeUnit.SECONDS); Assert.assertFalse("Unexpected obtained a read lock", readLockAcquired); } catch (ConcurrentAccessTimeoutException cate) { // expected } finally { // let the latch know that this thread is done with its part // of processing latch.countDown(); // now let's wait for the other thread to complete processing // and bringing down the count on the latch latch.await(); } } /** * Tests that a thread can first get a write lock and at a later point in time, get * a read lock * * @throws Exception */ @Test public void testSameThreadCanGetWriteThenReadLock() throws Exception { Lock writeLock = this.ejbReadWriteLock.writeLock(); // lock it! writeLock.lock(); Lock readLock = this.ejbReadWriteLock.readLock(); // lock it! (should work, because we are going from a write to read and *not* // the other way round) try { boolean readLockAcquired = readLock.tryLock(2, TimeUnit.SECONDS); // unlock the read lock, because we don't need it anymore if (readLockAcquired) { readLock.unlock(); } Assert.assertTrue("Could not obtain read lock when write lock was held by the same thread!", readLockAcquired); } finally { // unlock our write lock writeLock.unlock(); } } /** * An implementation of {@link Runnable} which in its {@link #run()} method * will first obtain a lock and then will go to sleep for the specified amount * of time. After processing, it will unlock the {@link java.util.concurrent.locks.Lock} * * @author Jaikiran Pai * @version $Revision: $ */ private class ThreadHoldingWriteLock implements Runnable { /** * Lock */ private Lock lock; /** * The amount of time, in milliseconds, this {@link ThreadHoldingWriteLock} * will sleep for in its {@link #run()} method */ private long processingTime; /** * A latch for notifying any waiting threads */ private CountDownLatch latch; /** * Creates a {@link ThreadHoldingWriteLock} * * @param latch A latch for notifying any waiting threads * @param lock A lock that will be used for obtaining a lock during processing * @param processingTime The amount of time in milliseconds, this thread will sleep (a.k.a process) * in its {@link #run()} method */ public ThreadHoldingWriteLock(CountDownLatch latch, Lock lock, long processingTime) { this.lock = lock; this.processingTime = processingTime; this.latch = latch; } /** * Obtains a lock, sleeps for {@link #processingTime} milliseconds and then unlocks the lock * * @see Runnable#run() */ @Override public void run() { // lock it! this.lock.lock(); // process(sleep) for the specified time try { Thread.sleep(this.processingTime); } catch (InterruptedException e) { // ignore } finally { // unlock this.lock.unlock(); // let any waiting threads know that we are done processing this.latch.countDown(); } } } }