/**
* Copyright 2005-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 org.junit.Before;
import org.junit.Test;
import com.persistit.exception.PersistitException;
import com.persistit.unit.ConcurrentUtil;
import com.persistit.unit.UnitTestProperties;
/**
* <p>
* Inspired by bug1126297: Assertion failure in
* TransactionIndexBucket#allocateTransactionStatus
* </p>
* <p>
* The symptom was the bug (a locked TransactionStatus on the free list) but the
* cause was mishandling of abandoned transactions from the
* {@link Persistit#cleanup()} method.
* </p>
* <p>
* When attempting to rollback the abandoned transaction, the status was
* notified and then unlocked. Since the lock was held by a now dead thread, an
* IllegalMonitorStateException occurred. It was then put on the free list
* during the next cleanup of that bucket since it had been notified.
* </p>
*/
public class TransactionAbandonedTest extends PersistitUnitTestCase {
private static final String TREE = TransactionAbandonedTest.class.getSimpleName();
private static final int KEY_START = 1;
private static final int KEY_RANGE = 10;
private static final long MAX_TIMEOUT_MS = 10 * 1000;
private static class TxnAbandoner extends ConcurrentUtil.ThrowingRunnable {
private final Persistit persistit;
private final boolean doRead;
private final boolean doWrite;
public TxnAbandoner(final Persistit persistit, final boolean doRead, final boolean doWrite) {
this.persistit = persistit;
this.doRead = doRead;
this.doWrite = doWrite;
}
@Override
public void run() throws PersistitException {
final Transaction txn = persistit.getTransaction();
txn.begin();
if (doRead) {
assertEquals("Traverse count", KEY_RANGE, scanAndCount(getExchange(persistit)));
}
if (doWrite) {
loadData(persistit, KEY_START + KEY_RANGE, KEY_RANGE);
}
}
}
private static Exchange getExchange(final Persistit persistit) throws PersistitException {
return persistit.getExchange(UnitTestProperties.VOLUME_NAME, TREE, true);
}
private static void loadData(final Persistit persistit, final int keyOffset, final int count)
throws PersistitException {
final Exchange ex = getExchange(persistit);
for (int i = 0; i < count; ++i) {
ex.clear().append(keyOffset + i).store();
}
}
private static int scanAndCount(final Exchange ex) throws PersistitException {
ex.clear().append(Key.BEFORE);
int saw = 0;
while (ex.next()) {
++saw;
}
return saw;
}
@Before
public void disableAndLoad() throws PersistitException {
disableBackgroundCleanup();
loadData(_persistit, KEY_START, KEY_RANGE);
}
private void runAndCleanup(final String name, final boolean doRead, final boolean doWrite) {
final Thread t = ConcurrentUtil.createThread(name, new TxnAbandoner(_persistit, false, false));
ConcurrentUtil.startAndJoinAssertSuccess(MAX_TIMEOUT_MS, t);
// Threw exception before fix
_persistit.cleanup();
}
@Test
public void noReadsOrWrites() {
runAndCleanup("NoReadNoWrite", false, false);
}
@Test
public void readOnly() throws PersistitException {
runAndCleanup("ReadOnly", true, false);
assertEquals("Traversed after abandoned", KEY_RANGE, scanAndCount(getExchange(_persistit)));
}
@Test
public void readAndWrite() throws Exception {
runAndCleanup("ReadAndWrite", true, true);
assertEquals("Traversed after abandoned", KEY_RANGE, scanAndCount(getExchange(_persistit)));
// Check that the abandoned was pruned
final CleanupManager cm = _persistit.getCleanupManager();
for (int i = 0; i < 5 && cm.getEnqueuedCount() > 0; ++i) {
cm.runTask();
}
final Exchange rawEx = getExchange(_persistit);
rawEx.ignoreMVCCFetch(true);
assertEquals("Raw traversed after abandoned", KEY_RANGE, scanAndCount(rawEx));
}
}