/**
* Copyright 2013 Akiban Technologies, 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.persistit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Semaphore;
import org.junit.Test;
import com.persistit.exception.InUseException;
import com.persistit.exception.InvalidKeyException;
import com.persistit.exception.PersistitException;
import com.persistit.unit.UnitTestProperties;
public class ExchangeLockTest extends PersistitUnitTestCase {
private final static long DMILLIS = SharedResource.DEFAULT_MAX_WAIT_TIME;
private final Semaphore _coordinator = new Semaphore(0);
@Override
public Properties getProperties(final boolean cleanup) {
return UnitTestProperties.getBiggerProperties(cleanup);
}
@Test
public void singleThreadedLock() throws Exception {
final Exchange ex = _persistit.getExchange("persistit", "ExchangeLockTest", true);
final Transaction txn = ex.getTransaction();
try {
ex.lock();
fail("Expected to fail");
} catch (final IllegalStateException e) {
// expected
}
txn.begin();
try {
try {
ex.lock();
fail("Expected to fail");
} catch (final InvalidKeyException e) {
// expected
}
ex.append("motor");
ex.lock();
final Tree tree = _persistit.getLockVolume().getTree("ExchangeLockTest", false);
assertTrue("Expected tree to be defined", tree != null);
final Exchange ex2 = new Exchange(tree);
ex2.ignoreMVCCFetch(true);
assertTrue("Expect a key in the temp volume", ex2.next(true));
txn.commit();
} catch (final Exception e) {
e.printStackTrace();
} finally {
txn.end();
}
txn.begin();
try {
ex.lock();
txn.commit();
} finally {
txn.end();
}
}
private class Locker implements Runnable {
final Semaphore _semaphore = new Semaphore(0);
final long _timeout;
final int[] _sequence;
Exception _exception;
volatile int _expectedReleases;
volatile boolean _committed;
private Locker(final long timeout, final int... sequence) {
_timeout = timeout;
_sequence = sequence;
}
private void go(final int waitFor) throws InterruptedException {
_semaphore.release();
_coordinator.acquire(waitFor);
}
private int cycles() {
return _sequence.length + 2;
}
@Override
public void run() {
try {
final Exchange ex = _persistit.getExchange("persistit", "ExchangeLockTest", true);
final Transaction txn = ex.getTransaction();
_expectedReleases = cycles();
txn.begin();
try {
for (final int k : _sequence) {
_semaphore.acquire();
ex.clear().append(k).lock(ex.getKey(), _timeout);
_coordinator.release();
_expectedReleases--;
}
_semaphore.acquire();
txn.commit();
_committed = true;
_coordinator.release();
_expectedReleases--;
} catch (final Exception e) {
_exception = e;
txn.rollback();
} finally {
if (_expectedReleases > 0) {
_coordinator.release(_expectedReleases);
}
_semaphore.acquire();
txn.end();
_coordinator.release();
_expectedReleases--;
}
} catch (final Exception e) {
e.printStackTrace();
}
}
}
private Thread[] start(final Locker... lockers) {
final Thread[] threads = new Thread[lockers.length];
int count = 0;
for (final Locker locker : lockers) {
final Thread t = new Thread(locker);
t.start();
threads[count++] = t;
}
return threads;
}
private void join(final Thread[] threads) throws InterruptedException {
for (final Thread t : threads) {
t.join();
}
}
@Test
public void nonConflictingLocks() throws Exception {
final Locker a = new Locker(DMILLIS, 1, 5);
final Locker b = new Locker(DMILLIS, 2, 6);
final Locker c = new Locker(DMILLIS, 3, 7);
final Locker d = new Locker(DMILLIS, 4, 8);
final Thread[] threads = start(a, b, c, d);
for (int i = 0; i < a.cycles(); i++) {
a.go(1);
b.go(1);
c.go(1);
d.go(1);
}
join(threads);
assertTrue(a._committed);
assertTrue(b._committed);
assertTrue(c._committed);
assertTrue(d._committed);
}
@Test
public void simpleConflictingLocks() throws Exception {
final Locker a = new Locker(1000, 1);
final Locker b = new Locker(1000, 1);
final Thread[] threads = start(a, b);
final long start = System.currentTimeMillis();
for (int i = 0; i < a.cycles(); i++) {
a.go(1);
b.go(1);
}
join(threads);
final long end = System.currentTimeMillis();
assertTrue(end - start >= 1000);
assertTrue(a._committed ^ b._committed);
}
@Test
public void deadlock() throws Exception {
final Locker a = new Locker(DMILLIS, 1, 2);
final Locker b = new Locker(DMILLIS, 2, 1);
final Thread[] threads = start(a, b);
final long start = System.currentTimeMillis();
for (int i = 0; i < a.cycles(); i++) {
a.go(i == 0 ? 1 : 0);
b.go(i == 0 ? 1 : 0);
}
join(threads);
final long end = System.currentTimeMillis();
assertTrue(end - start < DMILLIS);
assertTrue(a._committed ^ b._committed);
}
@Test
public void multiWayDeadlock() throws Exception {
final Locker a = new Locker(DMILLIS, 1, 2, 3, 4, 5);
final Locker b = new Locker(DMILLIS, 2, 3, 4, 5, 1);
final Locker c = new Locker(DMILLIS, 3, 4, 5, 1, 2);
final Locker d = new Locker(DMILLIS, 4, 5, 1, 2, 3);
final Locker e = new Locker(DMILLIS, 5, 1, 2, 3, 4);
final Thread[] threads = start(a, b, c, d, e);
final long start = System.currentTimeMillis();
for (int i = 0; i < a.cycles(); i++) {
final int w = (i == 0) ? 1 : 0;
a.go(w);
b.go(w);
c.go(w);
d.go(w);
e.go(w);
}
join(threads);
final long end = System.currentTimeMillis();
assertTrue(end - start < DMILLIS);
int succeeded = 0;
for (final Locker l : new Locker[] { a, b, c, d, e }) {
if (l._committed) {
succeeded++;
}
}
assertEquals(1, succeeded);
}
/**
* This test is intended to exercise lock management for transactions
* executed sequentially, each of which performs lots of locks (similar to
* DataLoadingTest.)
*
* @throws Exception
*/
@Test
public void lockTablePruning() throws Exception {
final Exchange ex = _persistit.getExchange("persistit", "ExchangeLockTest", true);
final Random random = new Random();
final Transaction txn = ex.getTransaction();
for (int j = 0; j < 100; j++) {
txn.begin();
for (int i = 0; i < 10000; i++) {
final int k = random.nextInt(100000);
ex.clear().append(k + (j * 100000)).append(RED_FOX).lock();
}
txn.commit();
txn.end();
}
assertTrue("Too many lock volume pages uses",
_persistit.getLockVolume().getStorage().getNextAvailablePage() < 100);
final Exchange lockExchange = new Exchange(_persistit.getLockVolume().getTree("ExchangeLockTest", false));
final int count = keyCount(lockExchange);
assertEquals("Unpruned lock records", 0, count);
}
private int keyCount(final Exchange ex) throws PersistitException {
int count = 0;
ex.clear();
while (ex.next(true)) {
count++;
}
return count;
}
@Test
public void timeout() throws Exception {
/*
* A cursory check of the Exchange timeout value
*/
final Exchange ex = _persistit.getExchange("persistit", "gogo", true);
ex.to("a").store();
final long latchedPage = ex.fetchBufferCopy(0).getPageAddress();
final BufferPool pool = ex.getBufferPool();
final Volume volume = ex.getVolume();
ex.setTimeoutMillis(1000);
final long start = System.currentTimeMillis();
final Semaphore a = new Semaphore(0);
final Semaphore b = new Semaphore(0);
new Thread(new Runnable() {
@Override
public void run() {
try {
final Buffer buffer = pool.get(volume, latchedPage, true, true);
a.release();
b.acquire();
buffer.release();
} catch (final Exception e) {
e.printStackTrace();
}
}
}).start();
a.acquire();
try {
ex.to("a").store();
fail("Expected an InUseException");
} catch (final InUseException e) {
// expected
}
final long end = System.currentTimeMillis();
b.release();
final long interval = end - start;
assertTrue("Should have waited about 1 second", interval >= 1000 && interval < 2000);
}
@Test
public void bug1125603test() throws Exception {
final Exchange ex = _persistit.getExchange("persistit", "ExchangeLockTest", true);
final Transaction txn = ex.getTransaction();
txn.begin();
try {
ex.append("motor");
ex.lock();
txn.commit();
} catch (final Exception e) {
e.printStackTrace();
} finally {
txn.end();
}
_persistit.getJournalManager().rolloverWithNewFile();
_persistit.checkpoint();
_persistit.close();
_persistit = new Persistit(_config);
_persistit.initialize();
}
}