/**
* 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 java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import com.persistit.exception.PersistitException;
import com.persistit.unit.ConcurrentUtil;
/**
* <p>
* Bug https://bugs.launchpad.net/akiban-persistit/+bug/1174352
* </p>
* <p>
* Documentation for com.persistit.Transaction says that with careful management
* of ownership of a SessionId, it is possible to complete a transaction in a
* different thread than the one that began it. However, this is not the case:
* the second thread receives an IllegalMonitorStateException when attempting to
* commit or abort the transaction.
* </p>
* <p>
* The issue is that the TransactionStatus object used to represent transaction
* state within the TransactionIndex uses a
* java.util.concurrent.locks.ReentrantLock to represent its in-use state, and
* only the thread that locked it may unlock it. *
* </p>
* <p>
*/
public class TransactionSessionSwitchTest extends PersistitUnitTestCase {
private final static int SESSIONS = 100;
private final static int STEPS = 197;
private final static int THREADS = 17;
private final static long TIMEOUT = 10000;
private final Queue<SessionId> sessionQueue = new ArrayBlockingQueue<SessionId>(SESSIONS);
private final Map<SessionId, AtomicInteger> sessionState = new HashMap<SessionId, AtomicInteger>();
@Test
public void sessionManagement() throws Exception {
for (int i = 0; i < SESSIONS; i++) {
final SessionId sessionId = new SessionId();
sessionQueue.add(sessionId);
sessionState.put(sessionId, new AtomicInteger(0));
}
final Thread[] threads = new Thread[THREADS];
final Tree tree = _persistit.getVolume("persistit").getTree("tt", true);
for (int i = 0; i < THREADS; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
SessionId session;
while ((session = sessionQueue.poll()) != null) {
final int state = sessionState.get(session).get();
try {
_persistit.setSessionId(session);
final Transaction txn = _persistit.getTransaction();
if (state == 0) {
txn.begin();
} else if (state > STEPS) {
if ((session.hashCode() % 3) == 0) {
txn.rollback();
} else {
txn.commit();
}
txn.end();
} else {
final Exchange ex = _persistit.getExchange(tree.getVolume(), tree.getName(), false);
ex.getValue().put(Thread.currentThread().getName());
ex.clear().append(session.hashCode()).append(state).store();
_persistit.releaseExchange(ex);
}
} catch (final PersistitException e) {
throw new RuntimeException(e);
} finally {
if (state <= STEPS) {
sessionState.get(session).incrementAndGet();
sessionQueue.add(session);
}
}
}
}
});
}
ConcurrentUtil.startAndJoinAssertSuccess(TIMEOUT, threads);
final Exchange ex = _persistit.getExchange(tree.getVolume(), tree.getName(), false);
for (final SessionId session : sessionState.keySet()) {
int count = 0;
ex.clear().append(session.hashCode()).append(Key.BEFORE);
while (ex.next()) {
count++;
}
final int expected = (session.hashCode() % 3) == 0 ? 0 : STEPS;
assertEquals("Mismatched count", expected, count);
}
}
}