
org.mapdb20.TxEngine Maven / Gradle / Ivy
/*
* Copyright (c) 2012 Jan Kotek
*
* 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 org.mapdb20;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.LinkedHashSet;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
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 implements Engine {
protected static final Object TOMBSTONE = new Object();
protected final ReentrantReadWriteLock commitLock = new ReentrantReadWriteLock(CC.FAIR_LOCKS);
protected final ReentrantReadWriteLock[] locks;
protected final int lockScale;
protected final int lockMask;
protected volatile boolean uncommitedData = false;
protected Set> txs = new LinkedHashSet>();
protected ReferenceQueue txQueue = new ReferenceQueue();
protected final boolean fullTx;
protected final Queue preallocRecids;
protected final int PREALLOC_RECID_SIZE = 128;
protected final Engine engine;
protected TxEngine(Engine engine, boolean fullTx, int lockScale) {
this.engine = engine;
this.fullTx = fullTx;
this.preallocRecids = fullTx ? new ArrayBlockingQueue(PREALLOC_RECID_SIZE) : null;
this.lockScale = lockScale;
this.lockMask = lockScale-1;
locks=new ReentrantReadWriteLock[lockScale];
{
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 = engine.preallocate();
Lock lock = locks[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 long put(A value, Serializer serializer) {
commitLock.readLock().lock();
try {
uncommitedData = true;
long recid = engine.put(value, serializer);
Lock lock = locks[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 engine.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[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);
}
engine.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[lockPos(recid)].writeLock();
lock.lock();
try{
boolean ret = engine.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[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);
}
engine.delete(recid, serializer);
}finally {
lock.unlock();
}
} finally {
commitLock.readLock().unlock();
}
}
@Override
public void close() {
commitLock.writeLock().lock();
try {
engine.close();
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public boolean isClosed() {
return engine.isClosed();
}
@Override
public void commit() {
commitLock.writeLock().lock();
try {
cleanTxQueue();
engine.commit();
uncommitedData = false;
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public void rollback() {
commitLock.writeLock().lock();
try {
cleanTxQueue();
engine.rollback();
uncommitedData = false;
} finally {
commitLock.writeLock().unlock();
}
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public boolean canRollback() {
return false;
}
protected void superCommit() {
if(CC.ASSERT && ! (commitLock.isWriteLockedByCurrentThread()))
throw new AssertionError();
engine.commit();
}
protected void superUpdate(long recid, A value, Serializer serializer) {
if(CC.ASSERT && ! (commitLock.isWriteLockedByCurrentThread()))
throw new AssertionError();
engine.update(recid, value, serializer);
}
protected void superDelete(long recid, Serializer serializer) {
if(CC.ASSERT && ! (commitLock.isWriteLockedByCurrentThread()))
throw new AssertionError();
engine.delete(recid, serializer);
}
protected A superGet(long recid, Serializer serializer) {
if(CC.ASSERT && ! (commitLock.isWriteLockedByCurrentThread()))
throw new AssertionError();
return engine.get(recid, serializer);
}
public class Tx implements Engine{
protected LongConcurrentHashMap old = new LongConcurrentHashMap();
protected LongConcurrentHashMap mod =
fullTx ? new LongConcurrentHashMap() : null;
protected final Reference ref = new WeakReference(this,txQueue);
protected boolean closed = false;
private Store parentEngine;
public Tx(){
if(CC.ASSERT && ! (commitLock.isWriteLockedByCurrentThread()))
throw new AssertionError();
txs.add(ref);
}
@Override
public long preallocate() {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
return preallocRecidTake();
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public long put(A value, Serializer serializer) {
if(!fullTx)
throw new UnsupportedOperationException("read-only");
commitLock.writeLock().lock();
try{
Long recid = preallocRecidTake();
mod.put(recid, new Fun.Pair>(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[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.Pair 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, new Fun.Pair(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[lockPos(recid)].writeLock();
lock.lock();
try{
A oldVal = getNoLock(recid, serializer);
boolean ret = oldVal==expectedOldValue ||
(oldVal!=null && serializer.equals(oldVal,expectedOldValue));
if(ret){
mod.put(recid,new Fun.Pair(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,new Fun.Pair(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("uncommitted data");
txs.remove(ref);
cleanTxQueue();
//check no other TX has modified our data
LongConcurrentHashMap.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();
}
}
}
LongConcurrentHashMap.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.Pair 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);
}
}
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("uncommitted data");
txs.remove(ref);
cleanTxQueue();
// TxEngine.this.superCommit();
close();
}finally {
commitLock.writeLock().unlock();
}
}
@Override
public boolean isReadOnly() {
return !fullTx;
}
@Override
public boolean canRollback() {
return fullTx;
}
@Override
public boolean canSnapshot() {
return false;
}
@Override
public Engine snapshot() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
//TODO see Issue #281
}
@Override
public Engine getWrappedEngine() {
return engine; //TODO should be exposed?
}
@Override
public void clearCache() {
}
@Override
public void compact() {
}
}
protected final int lockPos(final long recid) {
return DataIO.longHash(recid)&lockMask;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy