package com.thinkaurelius.titan.diskstorage.persistit;
import static com.thinkaurelius.titan.diskstorage.persistit.PersistitStoreManager.VOLUME_NAME;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.persistit.Exchange;
import com.persistit.Persistit;
import com.persistit.SessionId;
import com.persistit.Transaction;
import com.persistit.exception.PersistitException;
import com.persistit.exception.RollbackException;
import com.thinkaurelius.titan.diskstorage.PermanentStorageException;
import com.thinkaurelius.titan.diskstorage.StorageException;
import com.thinkaurelius.titan.diskstorage.common.AbstractStoreTransaction;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTxConfig;
/**
* @todo: read this and make sure multiple threads aren't sharing transactions http://akiban.github.com/persistit/javadoc/com/persistit/Transaction.html#_threadManagement
* @todo: add finalize method
*/
public class PersistitTransaction extends AbstractStoreTransaction {
private Persistit db;
private SessionId sessionId;
private static final Logger log = LoggerFactory.getLogger(PersistitTransaction.class);
private static Queue<SessionId> sessionPool = new ConcurrentLinkedQueue<SessionId>();
private static SessionId getSessionId() {
SessionId s = sessionPool.poll();
if (s == null) {
s = new SessionId();
}
return s;
}
private static void returnSessionId(SessionId s) {
sessionPool.offer(s);
}
public PersistitTransaction(Persistit p, StoreTxConfig config) throws StorageException {
super(config);
db = p;
synchronized (this) {
sessionId = getSessionId();
}
assign();
Preconditions.checkNotNull(sessionId);
Transaction tx = db.getTransaction();
assert sessionId == tx.getSessionId();
try {
tx.begin();
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
/**
* Assigns the session id to the current thread
*/
public void assign() {
synchronized (this) {
Preconditions.checkNotNull(sessionId);
db.setSessionId(sessionId);
}
}
private void close() {
returnSessionId(sessionId);
sessionId = null;
}
@Override
public void rollback() throws StorageException {
super.rollback();
synchronized (this) {
assign();
Transaction tx = db.getTransaction();
if (tx.isActive() && !tx.isCommitted()) {
tx.rollback();
}
tx.end();
close();
}
}
@Override
public void commit() throws StorageException {
synchronized (this) {
if (null == sessionId) { // Already closed
log.warn("Can't commit {}: already closed, trace to redundant commit follows", this, new IllegalStateException("redundant commit"));
return;
}
super.commit();
assign();
Transaction tx = db.getTransaction();
int retries = 3;
try {
if (tx.isActive() && !tx.isRollbackPending()) {
int i = 0;
while (true) {
try {
tx.commit(Transaction.CommitPolicy.HARD);
tx.end();
break;
} catch (RollbackException ex) {
if (i++ >= retries) {
throw ex;
}
}
}
close();
}
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
}
public Exchange getExchange(String treeName) throws StorageException {
return getExchange(treeName, true);
}
public Exchange getExchange(String treeName, Boolean create) throws StorageException {
synchronized (this) {
Exchange exchange;
try {
assign();
exchange = db.getExchange(VOLUME_NAME, treeName, create);
return exchange;
} catch (PersistitException ex) {
throw new PermanentStorageException(ex);
}
}
}
public void releaseExchange(Exchange exchange) {
//
}
}