All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mapdb.TxEngine Maven / Gradle / Ivy

Go to download

MapDB provides concurrent Maps, Sets and Queues backed by disk storage or off-heap memory. It is a fast, scalable and easy to use embedded Java database.

There is a newer version: 3.1.0
Show newest version
/*
 *  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.mapdb;

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