/* * Copyright 2016 higherfrequencytrading.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package net.openhft.lang.locks; import net.openhft.lang.io.ByteBufferBytes; import net.openhft.lang.io.Bytes; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import sun.nio.ch.DirectBuffer; import java.nio.ByteBuffer; import java.util.Collection; import java.util.concurrent.*; import static java.util.Arrays.asList; import static net.openhft.lang.locks.LockingStrategyTest.AccessMethod.ADDRESS; import static net.openhft.lang.locks.LockingStrategyTest.AccessMethod.BYTES_WITH_OFFSET; import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; @RunWith(value = Parameterized.class) public class LockingStrategyTest { ExecutorService e1, e2; ByteBuffer buffer; Bytes bytes; long offset; LockingStrategy lockingStrategy; AccessMethod accessMethod; NativeAtomicAccess access; Object accessObj; TestReadWriteLockState rwLockState = new TestReadWriteLockState(); Callable<Boolean> tryReadLockTask = new Callable<Boolean>() { @Override public Boolean call() { return rwls().tryReadLock(); } }; TestReadWriteUpdateLockState rwuLockState = new TestReadWriteUpdateLockState(); Runnable readUnlockTask = new Runnable() { @Override public void run() { rwls().readUnlock(); } }; Callable<Boolean> tryUpdateLockTask = new Callable<Boolean>() { @Override public Boolean call() { return rwuls().tryUpdateLock(); } }; Runnable updateUnlockTask = new Runnable() { @Override public void run() { rwuls().updateUnlock(); } }; Callable<Boolean> tryWriteLockTask = new Callable<Boolean>() { @Override public Boolean call() { return rwls().tryWriteLock(); } }; Runnable writeUnlockTask = new Runnable() { @Override public void run() { rwls().writeUnlock(); } }; public LockingStrategyTest(LockingStrategy lockingStrategy, AccessMethod accessMethod) { this.lockingStrategy = lockingStrategy; this.accessMethod = accessMethod; } @Parameterized.Parameters public static Collection<Object[]> data() { return asList(new Object[][]{ {VanillaReadWriteUpdateWithWaitsLockingStrategy.instance(), ADDRESS}, {VanillaReadWriteUpdateWithWaitsLockingStrategy.instance(), BYTES_WITH_OFFSET}, {VanillaReadWriteWithWaitsLockingStrategy.instance(), ADDRESS}, {VanillaReadWriteWithWaitsLockingStrategy.instance(), BYTES_WITH_OFFSET}, }); } @Before public void setUp() { e1 = new ThreadPoolExecutor(0, 1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); e2 = new ThreadPoolExecutor(0, 1, Integer.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); buffer = ByteBuffer.allocateDirect(8); bytes = new ByteBufferBytes(buffer); offset = accessMethod == ADDRESS ? ((DirectBuffer) buffer).address() : 0L; accessObj = accessMethod == BYTES_WITH_OFFSET ? bytes : null; access = accessMethod == ADDRESS ? NativeAtomicAccess.unsafe() : NativeAtomicAccess.toBytes(); rwls().reset(); } @After public void tearDown() { e1.shutdown(); e2.shutdown(); } ReadWriteLockState rwls() { return rwLockState; } ReadWriteUpdateLockState rwuls() { return rwuLockState; } @Test public void testUpdateLockIsExclusive() throws ExecutionException, InterruptedException { assumeReadWriteUpdateLock(); // Acquire the update lock in thread 1... assertTrue(e1.submit(tryUpdateLockTask).get()); // Try to acquire update lock in thread 2, should fail... assertFalse(e2.submit(tryUpdateLockTask).get()); // Release the update lock in thread 1... e1.submit(updateUnlockTask).get(); // Try to acquire update lock in thread 2 again, should succeed... assertTrue(e2.submit(tryUpdateLockTask).get()); // Release the update lock in thread 2... e2.submit(updateUnlockTask).get(); } @Test public void testUpdateLockAllowsOtherReaders() throws ExecutionException, InterruptedException { assumeReadWriteUpdateLock(); // Acquire the update lock in thread 1... assertTrue(e1.submit(tryUpdateLockTask).get()); // Try to acquire read lock in thread 2, should succeed... assertTrue(e2.submit(tryReadLockTask).get()); // Release the update lock in thread 1... e1.submit(updateUnlockTask).get(); // Release the read lock in thread 2... e2.submit(readUnlockTask).get(); } @Test public void testUpdateLockBlocksOtherWriters() throws ExecutionException, InterruptedException { assumeReadWriteUpdateLock(); // Acquire the update lock in thread 1... assertTrue(e1.submit(tryUpdateLockTask).get()); // Try to acquire write lock in thread 2, should fail... assertFalse(e2.submit(tryWriteLockTask).get()); // Release the update lock in thread 1... e1.submit(updateUnlockTask).get(); // Try to acquire write lock in thread 2 again, should succeed... assertTrue(e2.submit(tryWriteLockTask).get()); // Release the write lock in thread 2... e2.submit(writeUnlockTask).get(); } @Test public void testWriteLockBlocksOtherReaders() throws ExecutionException, InterruptedException { assumeReadWriteLock(); // Acquire the write lock in thread 1... assertTrue(e1.submit(tryWriteLockTask).get()); // Try to acquire read lock in thread 2, should fail... assertFalse(e2.submit(tryReadLockTask).get()); // Release the write lock in thread 1... e1.submit(writeUnlockTask).get(); // Try to acquire read lock in thread 2 again, should succeed... assertTrue(e2.submit(tryReadLockTask).get()); // Release the read lock in thread 2... e2.submit(readUnlockTask).get(); } @Test public void testUpdateLockUpgradeToWriteLock() throws ExecutionException, InterruptedException { assumeReadWriteUpdateLock(); // Acquire the update lock in thread 1... assertTrue(e1.submit(tryUpdateLockTask).get()); // Try to acquire write lock in thread 1, should succeed... assertTrue(e1.submit(new Callable<Boolean>() { @Override public Boolean call() { return rwuls().tryUpgradeUpdateToWriteLock(); } }).get()); // Release the write lock in thread 1... e1.submit(new Runnable() { @Override public void run() { rwuls().downgradeWriteToUpdateLock(); } }); // Release the update lock in thread 1... e1.submit(updateUnlockTask).get(); } @Test public void testReadWriteLockTransitions() { assumeReadWriteLock(); // forbid upgrades/downgrades/unlocks when lock is not held readUnlockForbidden(); writeUnlockForbidden(); upgradeReadToWriteLockForbidden(); downgradeWriteToReadLockForbidden(); // Read lock is held assertTrue(rwls().tryReadLock()); writeUnlockForbidden(); downgradeWriteToReadLockForbidden(); // allow unlock rwls().readUnlock(); assertTrue(rwls().tryReadLock()); // allow upgrade to write lock try { assertTrue(rwls().tryUpgradeReadToWriteLock()); } catch (UnsupportedOperationException tolerated) { rwls().readUnlock(); assertTrue(rwls().tryWriteLock()); } // write lock is held readUnlockForbidden(); upgradeReadToWriteLockForbidden(); // allow unlock rwls().writeUnlock(); assertTrue(rwls().tryWriteLock()); // allow downgrade to read lock try { rwls().downgradeWriteToReadLock(); } catch (UnsupportedOperationException tolerated) {} rwls().reset(); } void downgradeWriteToReadLockForbidden() { try { rwls().downgradeWriteToReadLock(); fail("downgradeWriteToReadLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void upgradeReadToWriteLockForbidden() { try { rwls().tryUpgradeReadToWriteLock(); fail("tryUpgradeReadToWriteLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void writeUnlockForbidden() { try { rwls().writeUnlock(); fail("writeUnlock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void readUnlockForbidden() { try { rwls().readUnlock(); fail("readUnlock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } @Test public void testReadWriteUpgradeLockTransitions() { assumeReadWriteUpdateLock(); // forbid upgrades/downgrades/unlocks when lock is not held updateUnlockForbidden(); upgradeReadToUpdateLockForbidden(); upgradeUpdateToWriteLockForbidden(); downgradeUpdateToReadLockForbidden(); downgradeWriteToUpdateLockForbidden(); // Read lock is held assertTrue(rwuls().tryReadLock()); updateUnlockForbidden(); upgradeUpdateToWriteLockForbidden(); downgradeUpdateToReadLockForbidden(); downgradeWriteToUpdateLockForbidden(); // allow upgrade to update lock assertTrue(rwuls().tryUpgradeReadToUpdateLock()); // update lock is held readUnlockForbidden(); writeUnlockForbidden(); upgradeReadToUpdateLockForbidden(); upgradeReadToWriteLockForbidden(); downgradeWriteToUpdateLockForbidden(); downgradeWriteToReadLockForbidden(); // allow unlock rwuls().updateUnlock(); assertTrue(rwuls().tryUpdateLock()); // allow upgrade to write lock assertTrue(rwuls().tryUpgradeUpdateToWriteLock()); // write lock is held updateUnlockForbidden(); upgradeReadToUpdateLockForbidden(); upgradeUpdateToWriteLockForbidden(); downgradeUpdateToReadLockForbidden(); // allow downgrade to update lock rwuls().downgradeWriteToUpdateLock(); rwuls().updateUnlock(); } void downgradeWriteToUpdateLockForbidden() { try { rwuls().downgradeWriteToUpdateLock(); fail("downgradeWriteToUpdateLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void downgradeUpdateToReadLockForbidden() { try { rwuls().downgradeUpdateToReadLock(); fail("downgradeUpdateToReadLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void upgradeUpdateToWriteLockForbidden() { try { rwuls().tryUpgradeUpdateToWriteLock(); fail("tryUpgradeUpdateToWriteLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void upgradeReadToUpdateLockForbidden() { try { rwuls().tryUpgradeReadToUpdateLock(); fail("tryUpgradeReadToUpdateLock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void updateUnlockForbidden() { try { rwuls().updateUnlock(); fail("updateUnlock() should fail"); } catch (IllegalMonitorStateException e) { // expected } catch (UnsupportedOperationException e2) { // expected } } void assumeReadWriteUpdateLock() { assumeTrue(lockingStrategy instanceof ReadWriteUpdateLockingStrategy); } void assumeReadWriteLock() { assumeTrue(lockingStrategy instanceof ReadWriteLockingStrategy); } enum AccessMethod {ADDRESS, BYTES_WITH_OFFSET} class TestReadWriteLockState extends AbstractReadWriteLockState { private ReadWriteLockingStrategy rwls() { return (ReadWriteLockingStrategy) lockingStrategy; } @Override public boolean tryReadLock() { return rwls().tryReadLock(access, accessObj, offset); } @Override public boolean tryWriteLock() { return rwls().tryWriteLock(access, accessObj, offset); } @Override public boolean tryUpgradeReadToWriteLock() { return rwls().tryUpgradeReadToWriteLock(access, accessObj, offset); } @Override public void readUnlock() { rwls().readUnlock(access, accessObj, offset); } @Override public void writeUnlock() { rwls().writeUnlock(access, accessObj, offset); } @Override public void downgradeWriteToReadLock() { rwls().downgradeWriteToReadLock(access, accessObj, offset); } @Override public void reset() { rwls().reset(access, accessObj, offset); } @Override public long getState() { return rwls().getState(access, accessObj, offset); } @Override public ReadWriteLockingStrategy lockingStrategy() { return rwls(); } } class TestReadWriteUpdateLockState extends TestReadWriteLockState implements ReadWriteUpdateLockState { ReadWriteUpdateLockingStrategy rwuls() { return (ReadWriteUpdateLockingStrategy) lockingStrategy; } @Override public boolean tryUpdateLock() { return rwuls().tryUpdateLock(access, accessObj, offset); } @Override public boolean tryUpgradeReadToUpdateLock() { return rwuls().tryUpgradeReadToUpdateLock(access, accessObj, offset); } @Override public boolean tryUpgradeUpdateToWriteLock() { return rwuls().tryUpgradeUpdateToWriteLock(access, accessObj, offset); } @Override public void updateUnlock() { rwuls().updateUnlock(access, accessObj, offset); } @Override public void downgradeUpdateToReadLock() { rwuls().downgradeUpdateToReadLock(access, accessObj, offset); } @Override public void downgradeWriteToUpdateLock() { rwuls().downgradeWriteToUpdateLock(access, accessObj, offset); } @Override public ReadWriteUpdateLockingStrategy lockingStrategy() { return rwuls(); } } }