Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/AbstractISMLockingTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/AbstractISMLockingTest.java (revision 736595) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/state/AbstractISMLockingTest.java (working copy) @@ -19,6 +19,8 @@ import junit.framework.TestCase; import org.apache.jackrabbit.core.NodeId; import org.apache.jackrabbit.core.ItemId; +import org.apache.jackrabbit.core.state.ISMLocking.ReadLock; +import org.apache.jackrabbit.core.state.ISMLocking.WriteLock; import org.apache.jackrabbit.uuid.UUID; import org.apache.jackrabbit.spi.commons.name.NameConstants; @@ -27,11 +29,13 @@ import java.util.Iterator; /** - * AbstractISMLockingTest contains test cases for the ISMLocking - * requirements. + * AbstractISMLockingTest contains test cases for the ISMLocking requirements. */ public abstract class AbstractISMLockingTest extends TestCase { + /** + * The {@link ISMLocking} instance under test. + */ protected ISMLocking locking; /** @@ -71,190 +75,175 @@ } /** - * Checks the following requirement: - *

- * While a read lock is held for a given item with id I an - * implementation must ensure that no write lock is issued for a change log - * that contains a reference to an item with id I. + * @return an {@link ISMLocking} instance which is exercised by the tests */ + public abstract ISMLocking createISMLocking(); + + /** + * Checks the following requirement:

While a read lock is held for a given item with id + * I an implementation must ensure that no write lock is issued for a change log that + * contains a reference to an item with id I. + * + * @throws InterruptedException on interruption; this will err the test + */ public void testReadBlocksWrite() throws InterruptedException { - ISMLocking.ReadLock rLock = locking.acquireReadLock(state.getId()); - try { - for (Iterator it = logs.iterator(); it.hasNext(); ) { - final ChangeLog log = (ChangeLog) it.next(); - Thread t = new Thread(new Runnable() { - public void run() { - try { - checkBlocking(log); - } catch (InterruptedException e) { - fail(e.toString()); - } - } - }); - t.start(); - t.join(); - } - } finally { - rLock.release(); + ReadLock rLock = locking.acquireReadLock(state.getId()); + for (Iterator it = logs.iterator(); it.hasNext();) { + ChangeLog changeLog = (ChangeLog) it.next(); + verifyBlocked(startWriterThread(locking, changeLog)); } + rLock.release(); } /** - * Checks the following requirement: - *

- * While a write lock is held for a given change log C an - * implementation must ensure that no read lock is issued for an item that - * is contained in C, unless the current thread is the owner of - * the write lock! + * Checks the following requirement:

While a write lock is held for a given change log + * C an implementation must ensure that no read lock is issued for an item that is contained + * in C, unless the current thread is the owner of the write lock!

The "unless" + * clause is tested by {@link #testWriteBlocksRead_notIfSameThread()} test. + * + * @throws InterruptedException on interruption; this will err the test */ public void testWriteBlocksRead() throws InterruptedException { - for (Iterator it = logs.iterator(); it.hasNext(); ) { - ISMLocking.WriteLock wLock - = locking.acquireWriteLock((ChangeLog) it.next()); - try { - checkNonBlocking(state.getId()); - Thread t = new Thread(new Runnable() { - public void run() { - try { - checkBlocking(state.getId()); - } catch (InterruptedException e) { - fail(e.toString()); - } + for (Iterator it = logs.iterator(); it.hasNext();) { + ChangeLog changeLog = (ChangeLog) it.next(); + WriteLock wLock = locking.acquireWriteLock(changeLog); + verifyBlocked(startReaderThread(locking, state.getId())); + wLock.release(); + } + } + + public void testWriteBlocksRead_notIfSameThread() throws InterruptedException { + for (Iterator it = logs.iterator(); it.hasNext();) { + final ChangeLog changeLog = (ChangeLog) it.next(); + Thread t = new Thread(new Runnable() { + + public void run() { + try { + WriteLock wLock = locking.acquireWriteLock(changeLog); + locking.acquireReadLock(state.getId()).release(); + wLock.release(); + } catch (InterruptedException e) { } - }); - t.start(); - t.join(); - } finally { - wLock.release(); - } + } + }); + t.start(); + verifyNotBlocked(t); } } /** - * Checks the following requirement: - *

- * While a write lock is held for a given change log C an - * implementation must ensure that no write lock is issued for a change log - * C' that intersects with C. That is both change - * logs contain a reference to the same item. Please note that an - * implementation is free to block requests entirely for additional write - * lock while a write lock is active. It is not a requirement to support - * concurrent write locks. + * Checks the following requirement:

While a write lock is held for a given change log C + * an implementation must ensure that no write lock is issued for a change log C' that intersects + * with C. That is both change logs contain a reference to the same item. Please note that an + * implementation is free to block requests entirely for additional write lock while a write lock is + * active. It is not a requirement to support concurrent write locks. + * + * @throws InterruptedException on interruption; this will err the test */ public void testIntersectingWrites() throws InterruptedException { ChangeLog cl = new ChangeLog(); cl.added(state); - ISMLocking.WriteLock wLock = locking.acquireWriteLock(cl); - try { - for (Iterator it = logs.iterator(); it.hasNext(); ) { - final ChangeLog log = (ChangeLog) it.next(); - Thread t = new Thread(new Runnable() { - public void run() { - try { - checkBlocking(log); - } catch (InterruptedException e) { - fail(e.toString()); - } - } - }); - t.start(); - t.join(); - } - } finally { - wLock.release(); + WriteLock wLock = locking.acquireWriteLock(cl); + for (Iterator it = logs.iterator(); it.hasNext();) { + ChangeLog changeLog = (ChangeLog) it.next(); + verifyBlocked(startWriterThread(locking, changeLog)); } + wLock.release(); } /** * Checks if a downgraded write lock allows other threads to read again. + * + * @throws InterruptedException on interruption; this will err the test */ public void testDowngrade() throws InterruptedException { - for (Iterator it = logs.iterator(); it.hasNext(); ) { - ISMLocking.ReadLock rLock = locking.acquireWriteLock( - (ChangeLog) it.next()).downgrade(); - try { - Thread t = new Thread(new Runnable() { - public void run() { - try { - checkNonBlocking(state.getId()); - } catch (InterruptedException e) { - fail(e.toString()); - } - } - }); - t.start(); - t.join(); - } finally { - rLock.release(); - } + for (Iterator it = logs.iterator(); it.hasNext();) { + ChangeLog changeLog = (ChangeLog) it.next(); + WriteLock wLock = locking.acquireWriteLock(changeLog); + verifyBlocked(startReaderThread(locking, state.getId())); + ReadLock rLock = wLock.downgrade(); + verifyNotBlocked(startReaderThread(locking, state.getId())); + rLock.release(); } } - //------------------------------< utilities >------------------------------- + // ------------------------------< utilities >------------------------------- - protected void checkBlocking(ChangeLog log) - throws InterruptedException { - final Thread t = Thread.currentThread(); - TimeBomb tb = new TimeBomb(100) { - public void explode() { - t.interrupt(); + /** + * Creates and starts a thread that acquires and releases the write lock of the given + * ISMLocking for the given changelog. The thread's interrupted status is set if it was + * interrupted during the acquire-release sequence and could therefore not finish it. + * + * @param lock the ISMLocking to use + * @param changeLog the ChangeLog to use + * @return a thread that has been started + */ + protected final Thread startWriterThread(final ISMLocking lock, final ChangeLog changeLog) { + Thread t = new Thread(new Runnable() { + + public void run() { + try { + lock.acquireWriteLock(changeLog).release(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } - }; - tb.arm(); - try { - locking.acquireWriteLock(log).release(); - } catch (InterruptedException e) { - // success - return; - } - tb.disarm(); - // make sure interrupted status is cleared - // bomb may blow off right before we disarm it - Thread.interrupted(); - fail("acquireWriteLock must block"); + }); + t.start(); + return t; } - protected void checkBlocking(ItemId id) - throws InterruptedException { - final Thread t = Thread.currentThread(); - TimeBomb tb = new TimeBomb(100) { - public void explode() { - t.interrupt(); + /** + * Creates and starts an thread that acquires and releases the read lock of the given + * ISMLocking for the given id. The thread's interrupted status is set if it was interrupted + * during the acquire-release sequence and could therefore not finish it. + * + * @param lock the ISMLocking to use + * @param id the id to use + * @return a thread that has been started + */ + protected final Thread startReaderThread(final ISMLocking lock, final ItemId id) { + Thread t = new Thread(new Runnable() { + + public void run() { + try { + lock.acquireReadLock(id).release(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } - }; - tb.arm(); - try { - locking.acquireReadLock(id).release(); - } catch (InterruptedException e) { - // success - return; - } - tb.disarm(); - // make sure interrupted status is cleared - // bomb may blow off right before we disarm it - Thread.interrupted(); - fail("acquireReadLock must block"); + }); + t.start(); + return t; } - protected void checkNonBlocking(ItemId id) - throws InterruptedException { - final Thread t = Thread.currentThread(); - TimeBomb tb = new TimeBomb(100) { - public void explode() { - t.interrupt(); - } - }; - tb.arm(); - try { - locking.acquireReadLock(id).release(); - } catch (InterruptedException e) { - fail("acquireReadLock must not block"); - } - tb.disarm(); - // make sure interrupted status is cleared - // bomb may blow off right before we disarm it - Thread.interrupted(); + /** + * Verifies that the given thread is blocked. Then it interrupts the thread and waits a certain amount of + * time for it to complete. (If it doesn't complete within that time then the test that calls this method + * fails). + * + * @param other a started thread + * @throws InterruptedException on interruption + */ + protected final void verifyBlocked(final Thread other) throws InterruptedException { + Thread.sleep(100); + assertTrue(other.isAlive()); + other.interrupt(); + other.join(100); + assertFalse(other.isAlive()); } - public abstract ISMLocking createISMLocking(); + /** + * Verifies that the given thread is not blocked and runs to completion within a certain amount of time + * and without interruption. (If it doesn't complete within that time without interruption then the test + * that calls this method fails). + * + * @param other a started thread + * @throws InterruptedException on interruption + */ + protected final void verifyNotBlocked(final Thread other) throws InterruptedException { + other.join(1000); + assertFalse(other.isInterrupted()); + assertFalse(other.isAlive()); + } }