org.mapdb.TxEngine Maven / Gradle / Ivy
package org.mapdb;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Naive implementation of Snapshots on top of StorageEngine.
* On update it takes old value and stores it aside.
*
* TODO merge snapshots down with Storage for best performance
*
* @author Jan Kotek
*/
public class TxEngine extends EngineWrapper {
protected static final Object TOMBSTONE = new Object();
protected final ReentrantReadWriteLock commitLock = new ReentrantReadWriteLock(CC.FAIR_LOCKS);
protected final ReentrantReadWriteLock[] locks = new ReentrantReadWriteLock[CC.CONCURRENCY];
{
for(int i=0;i> txs = new LinkedHashSet>();
protected ReferenceQueue txQueue = new ReferenceQueue();
protected final boolean fullTx;
protected final Queue preallocRecids;
protected final int PREALLOC_RECID_SIZE = 128;
protected TxEngine(Engine engine, boolean fullTx) {
super(engine);
this.fullTx = fullTx;
this.preallocRecids = fullTx ? new ArrayBlockingQueue(PREALLOC_RECID_SIZE) : null;
}
protected Long preallocRecidTake() {
assert(commitLock.isWriteLockedByCurrentThread());
Long recid = preallocRecids.poll();
if(recid!=null) return recid;
if(uncommitedData)
throw new IllegalAccessError("uncommited data");
for(int i=0;i ref = txQueue.poll(); ref!=null; ref=txQueue.poll()){
txs.remove(ref);
}
}
@Override
public long preallocate() {
commitLock.writeLock().lock();
try {
uncommitedData = true;
long recid = super.preallocate();
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,TOMBSTONE);
}
}finally {
lock.unlock();
}
return recid;
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public void preallocate(long[] recids) {
commitLock.writeLock().lock();
try {
uncommitedData = true;
super.preallocate(recids);
for(long recid:recids){
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,TOMBSTONE);
}
}finally {
lock.unlock();
}
}
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public long put(A value, Serializer serializer) {
commitLock.readLock().lock();
try {
uncommitedData = true;
long recid = super.put(value, serializer);
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,TOMBSTONE);
}
}finally {
lock.unlock();
}
return recid;
} finally {
commitLock.readLock().unlock();
}
}
@Override
public A get(long recid, Serializer serializer) {
commitLock.readLock().lock();
try {
return super.get(recid, serializer);
} finally {
commitLock.readLock().unlock();
}
}
@Override
public void update(long recid, A value, Serializer serializer) {
commitLock.readLock().lock();
try {
uncommitedData = true;
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
Object old = get(recid,serializer);
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,old);
}
super.update(recid, value, serializer);
}finally {
lock.unlock();
}
} finally {
commitLock.readLock().unlock();
}
}
@Override
public boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) {
commitLock.readLock().lock();
try {
uncommitedData = true;
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
boolean ret = super.compareAndSwap(recid, expectedOldValue, newValue, serializer);
if(ret){
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,expectedOldValue);
}
}
return ret;
}finally {
lock.unlock();
}
} finally {
commitLock.readLock().unlock();
}
}
@Override
public void delete(long recid, Serializer serializer) {
commitLock.readLock().lock();
try {
uncommitedData = true;
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
Object old = get(recid,serializer);
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null) continue;
tx.old.putIfAbsent(recid,old);
}
super.delete(recid, serializer);
}finally {
lock.unlock();
}
} finally {
commitLock.readLock().unlock();
}
}
@Override
public void close() {
commitLock.writeLock().lock();
try {
super.close();
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public void commit() {
commitLock.writeLock().lock();
try {
cleanTxQueue();
super.commit();
uncommitedData = false;
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public void rollback() {
commitLock.writeLock().lock();
try {
cleanTxQueue();
super.rollback();
uncommitedData = false;
} finally {
commitLock.writeLock().unlock();
}
}
protected void superCommit() {
assert(commitLock.isWriteLockedByCurrentThread());
super.commit();
}
protected void superUpdate(long recid, A value, Serializer serializer) {
assert(commitLock.isWriteLockedByCurrentThread());
super.update(recid,value,serializer);
}
protected void superDelete(long recid, Serializer serializer) {
assert(commitLock.isWriteLockedByCurrentThread());
super.delete(recid,serializer);
}
protected A superGet(long recid, Serializer serializer) {
assert(commitLock.isWriteLockedByCurrentThread());
return super.get(recid,serializer);
}
public class Tx implements Engine{
protected LongConcurrentHashMap old = new LongConcurrentHashMap();
protected LongConcurrentHashMap mod =
fullTx ? new LongConcurrentHashMap() : null;
protected Collection usedPreallocatedRecids =
fullTx ? new ArrayList() : null;
protected final Reference ref = new WeakReference(this,txQueue);
protected boolean closed = false;
private Store parentEngine;
public Tx(){
assert(commitLock.isWriteLockedByCurrentThread());
txs.add(ref);
}
@Override
public long preallocate() {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
Long recid = preallocRecidTake();
usedPreallocatedRecids.add(recid);
return recid;
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public void preallocate(long[] recids) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
for(int i=0;i long put(A value, Serializer serializer) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
Long recid = preallocRecidTake();
usedPreallocatedRecids.add(recid);
mod.put(recid, Fun.t2(value,serializer));
return recid;
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public A get(long recid, Serializer serializer) {
commitLock.readLock().lock();
try{
if(closed) throw new IllegalAccessError("closed");
Lock lock = locks[Store.lockPos(recid)].readLock();
lock.lock();
try{
return getNoLock(recid, serializer);
}finally {
lock.unlock();
}
}finally {
commitLock.readLock().unlock();
}
}
private A getNoLock(long recid, Serializer serializer) {
if(fullTx){
Fun.Tuple2 tu = mod.get(recid);
if(tu!=null){
if(tu.a==TOMBSTONE)
return null;
return (A) tu.a;
}
}
Object oldVal = old.get(recid);
if(oldVal!=null){
if(oldVal==TOMBSTONE)
return null;
return (A) oldVal;
}
return TxEngine.this.get(recid, serializer);
}
@Override
public void update(long recid, A value, Serializer serializer) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.readLock().lock();
try{
mod.put(recid, Fun.t2(value,serializer));
}finally {
commitLock.readLock().unlock();
}
}
@Override
public boolean compareAndSwap(long recid, A expectedOldValue, A newValue, Serializer serializer) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.readLock().lock();
try{
Lock lock = locks[Store.lockPos(recid)].writeLock();
lock.lock();
try{
A oldVal = getNoLock(recid, serializer);
boolean ret = oldVal!=null && oldVal.equals(expectedOldValue);
if(ret){
mod.put(recid,Fun.t2(newValue,serializer));
}
return ret;
}finally {
lock.unlock();
}
}finally {
commitLock.readLock().unlock();
}
}
@Override
public void delete(long recid, Serializer serializer) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.readLock().lock();
try{
mod.put(recid,Fun.t2(TOMBSTONE,serializer));
}finally {
commitLock.readLock().unlock();
}
}
@Override
public void close() {
closed = true;
old.clear();
ref.clear();
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public void commit() {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
if(closed) return;
if(uncommitedData)
throw new IllegalAccessError("uncomitted data");
txs.remove(ref);
cleanTxQueue();
if(pojo.hasUnsavedChanges())
pojo.save(this);
//check no other TX has modified our data
LongMap.LongMapIterator oldIter = old.longMapIterator();
while(oldIter.moveToNext()){
long recid = oldIter.key();
for(Reference ref2:txs){
Tx tx = ref2.get();
if(tx==this||tx==null) continue;
if(tx.mod.containsKey(recid)){
close();
throw new TxRollbackException();
}
}
}
LongMap.LongMapIterator iter = mod.longMapIterator();
while(iter.moveToNext()){
long recid = iter.key();
if(old.containsKey(recid)){
close();
throw new TxRollbackException();
}
}
iter = mod.longMapIterator();
while(iter.moveToNext()){
long recid = iter.key();
Fun.Tuple2 val = iter.value();
Serializer ser = (Serializer) val.b;
Object old = superGet(recid,ser);
if(old==null)
old = TOMBSTONE;
for(Reference txr:txs){
Tx tx = txr.get();
if(tx==null||tx==this) continue;
tx.old.putIfAbsent(recid,old);
}
if(val.a==TOMBSTONE){
superDelete(recid, ser);
}else {
superUpdate(recid, val.a, ser);
}
}
//there are no conflicts, so update the POJO in parent
//TODO sort of hack, is it thread safe?
getWrappedEngine().getSerializerPojo().registered = pojo.registered;
superCommit();
close();
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public void rollback() throws UnsupportedOperationException {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
if(closed) return;
if(uncommitedData)
throw new IllegalAccessError("uncomitted data");
txs.remove(ref);
cleanTxQueue();
for(Long prealloc:usedPreallocatedRecids){
TxEngine.this.superDelete(prealloc,null);
}
TxEngine.this.superCommit();
close();
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public boolean isReadOnly() {
return !fullTx;
}
@Override
public boolean canRollback() {
return fullTx;
}
@Override
public void clearCache() {
}
@Override
public void compact() {
}
SerializerPojo pojo = new SerializerPojo((CopyOnWriteArrayList) TxEngine.this.getSerializerPojo().registered.clone());
@Override
public SerializerPojo getSerializerPojo() {
return pojo;
}
public Engine getWrappedEngine() {
return TxEngine.this.getWrappedEngine();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy