/* * Copyright (c) 2013-2017 Cinchapi Inc. * * 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 com.cinchapi.concourse.server.concurrent; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock; import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock; import org.junit.Assert; import org.junit.Test; import com.cinchapi.concourse.server.concurrent.LockType; import com.cinchapi.concourse.server.concurrent.RangeLockService; import com.cinchapi.concourse.server.concurrent.RangeToken; import com.cinchapi.concourse.server.model.Text; import com.cinchapi.concourse.server.model.Value; import com.cinchapi.concourse.test.ConcourseBaseTest; import com.cinchapi.concourse.test.Variables; import com.cinchapi.concourse.thrift.Operator; import com.cinchapi.concourse.util.Convert; import com.cinchapi.concourse.util.TestData; /** * Unit tests for {@link RangeLockService}. * * @author Jeff Nelson */ public class RangeLockServiceTest extends ConcourseBaseTest { private RangeLockService rangeLockService; @Override protected void beforeEachTest() { rangeLockService = RangeLockService.create(); } @Test public void testWriteIsRangeBlockedIfReadingAllValues() { ReadLock readLock = rangeLockService.getReadLock(RangeToken.forReading( Text.wrapCached("foo"), Operator.BETWEEN, Value.NEGATIVE_INFINITY, Value.POSITIVE_INFINITY)); readLock.lock(); Assert.assertTrue(rangeLockService.isRangeBlocked( LockType.RANGE_WRITE, RangeToken.forWriting(Text.wrapCached("foo"), TestData.getValue()))); readLock.unlock(); } @Test public void testLockServiceDoesNotEvictLocksThatAreBeingUsed() throws InterruptedException { final AtomicBoolean done = new AtomicBoolean(false); final AtomicBoolean passed = new AtomicBoolean(true); Thread a = new Thread(new Runnable() { @Override public void run() { while (!done.get()) { try { ReadLock readLock = rangeLockService.getReadLock("foo", Operator.EQUALS, Convert.javaToThrift(1)); readLock.lock(); readLock.unlock(); } catch (IllegalMonitorStateException e) { e.printStackTrace(); passed.set(false); done.set(true); break; } } } }); Thread b = new Thread(new Runnable() { @Override public void run() { while (!done.get()) { try { WriteLock writeLock = rangeLockService.getWriteLock( "foo", Convert.javaToThrift(1)); writeLock.lock(); writeLock.unlock(); } catch (IllegalMonitorStateException e) { e.printStackTrace(); passed.set(false); done.set(true); break; } } } }); Thread c = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(TestData.getScaleCount() * 10); done.set(true); } catch (InterruptedException e) { e.printStackTrace(); } } }); a.start(); b.start(); TestData.getScaleCount(); // make sure that a and b start first c.start(); a.join(); b.join(); c.join(); Assert.assertTrue(passed.get()); } @Test public void testWriteLtLowerValueIsNotRangeBlockedIfReadingBw() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value1 = Variables.register("value1", TestData.getValue()); final Value value2 = Variables.register("value2", increase(value1)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getReadLock(key, Operator.BETWEEN, value1, value2).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getReadLock(key, Operator.BETWEEN, value1, value2).unlock(); } }); t.start(); startLatch.await(); Value value3 = Variables.register("value3", decrease(value1)); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.WRITE, RangeToken.forReading(key, null, value3))); finishLatch.countDown(); } @Test public void testWriteGtHigherValueIsNotRangeBlockedIfReadingBw() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value1 = Variables.register("value1", TestData.getValue()); final Value value2 = Variables.register("value2", increase(value1)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getReadLock(key, Operator.BETWEEN, value1, value2).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getReadLock(key, Operator.BETWEEN, value1, value2).unlock(); } }); t.start(); startLatch.await(); Value value3 = Variables.register("value3", increase(value2)); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.WRITE, RangeToken.forWriting(key, value3))); finishLatch.countDown(); } @Test public void testReadEqualsIsRangeBlockedIfWritingSameValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.EQUALS, value))); finishLatch.countDown(); } @Test public void testReadGteIsNotRangeBlockedIfWritingLtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltValue = Variables.register("ltValue", decrease(value)); rangeLockService.getWriteLock(key, ltValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadGteisRangeBlockedIfWritingEqValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadGteisRangeBlockedIfWritingGtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value gtValue = Variables.register("gtValue", increase(value)); rangeLockService.getWriteLock(key, gtValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, gtValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadGtIsNotRangeBlockedIfWritingEqValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN, value))); finishLatch.countDown(); } @Test public void testReadGtIsNotRangeBlockedIfWritingLtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltValue = Variables.register("ltValue", decrease(value)); rangeLockService.getWriteLock(key, ltValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN, value))); finishLatch.countDown(); } @Test public void testReadGtisRangeBlockedIfWritingGtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value gtValue = Variables.register("gtValue", increase(value)); rangeLockService.getWriteLock(key, gtValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, gtValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.GREATER_THAN, value))); finishLatch.countDown(); } @Test public void testReadLteIsNotRangeBlockedIfWritingGtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value gtValue = Variables.register("gtValue", increase(value)); rangeLockService.getWriteLock(key, gtValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, gtValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService .isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadLteisRangeBlockedIfWritingEqValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService .isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadLteisRangeBlockedIfWritingLtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltValue = Variables.register("ltValue", decrease(value)); rangeLockService.getWriteLock(key, ltValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService .isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN_OR_EQUALS, value))); finishLatch.countDown(); } @Test public void testReadLtIsNotBlockedIfWritingGtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value gtValue = Variables.register("gtValue", increase(value)); rangeLockService.getWriteLock(key, gtValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, gtValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN, value))); finishLatch.countDown(); } @Test public void testReadLtIsNotRangeBlockedIfWritingEqValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN, value))); finishLatch.countDown(); } @Test public void testReadLtIsRangeBlockedIfWritingLtValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltValue = Variables.register("ltValue", decrease(value)); rangeLockService.getWriteLock(key, ltValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.LESS_THAN, value))); finishLatch.countDown(); } @Test public void testReadBwisRangeBlockedIfWritingGtLowerValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value gtLowerValue = Variables.register("gtLowerValue", increase(value, value1)); rangeLockService.getWriteLock(key, gtLowerValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, gtLowerValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testReadBwIsNotRangeBlockedIfWritingLtLowerValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltLowerValue = Variables.register("ltLowerValue", decrease(value)); rangeLockService.getWriteLock(key, ltLowerValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltLowerValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testReadBwIsRangedBlockedIfWritingEqLowerValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testReadBwIsRangedBlockedIfWritingLtHigherValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value ltHigherValue = Variables.register("ltHigherValue", decrease(value1, value)); rangeLockService.getWriteLock(key, ltHigherValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, ltHigherValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testReadBwIsNotRangeBlockedIfWritingEqHigherValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getWriteLock(key, value1).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, value1).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testReadBwIsNotRangeBlockedIfWritingGtHigherValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final Value value1 = Variables.register("value1", increase(value)); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { Value getHigherValue = Variables.register("gtHigherValue", increase(value1)); rangeLockService.getWriteLock(key, getHigherValue).lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getWriteLock(key, getHigherValue).unlock(); } }); t.start(); startLatch.await(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.READ, RangeToken.forReading(key, Operator.BETWEEN, value, value1))); finishLatch.countDown(); } @Test public void testWriteIsRangeBlockedIfReadingEqualValue() throws InterruptedException { final Text key = Variables.register("key", TestData.getText()); final Value value = Variables.register("value", TestData.getValue()); final CountDownLatch startLatch = new CountDownLatch(1); final CountDownLatch finishLatch = new CountDownLatch(1); Thread t = new Thread(new Runnable() { @Override public void run() { rangeLockService.getReadLock(key, Operator.EQUALS, value) .lock(); startLatch.countDown(); try { finishLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } rangeLockService.getReadLock(key, Operator.EQUALS, value) .unlock(); } }); t.start(); startLatch.await(); Assert.assertTrue(rangeLockService.isRangeBlocked(LockType.WRITE, RangeToken.forWriting(key, value))); finishLatch.countDown(); } @Test public void testSameThreadNotRangeBlockedIfReadingRangeThatCoversHeldWrite() { WriteLock lock = rangeLockService.getWriteLock("foo", Convert.javaToThrift(10)); lock.lock(); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.WRITE, RangeToken.forReading(Text.wrapCached("foo"), Operator.BETWEEN, Value.wrap(Convert.javaToThrift(5)), Value.wrap(Convert.javaToThrift(15))))); lock.unlock(); } @Test public void testWriteNotRangeBlockedIfNoReading() { Text key = Variables.register("key", TestData.getText()); Value value = Variables.register("value", TestData.getValue()); Assert.assertFalse(rangeLockService.isRangeBlocked(LockType.WRITE, RangeToken.forWriting(key, value))); } private Value decrease(Value value) { Value lt = null; while (lt == null || lt.compareTo(value) >= 0) { lt = TestData.getValue(); } return lt; } private Value decrease(Value value, Value butKeepHigherThan) { Value lt = null; while (lt == null || lt.compareTo(butKeepHigherThan) <= 0) { lt = decrease(value); } return lt; } private Value increase(Value value, Value butKeepLowerThan) { Value gt = null; while (gt == null || gt.compareTo(butKeepLowerThan) >= 0) { gt = increase(value); } return gt; } /** * Return a Value that is greater than {@code value} * * @param value * @return the greater value */ private Value increase(Value value) { Value gt = null; while (gt == null || gt.compareTo(value) <= 0) { gt = TestData.getValue(); } return gt; } }