org.mapdb.AsyncWriteEngine 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.mapdb;
import java.lang.ref.WeakReference;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* {@link Engine} wrapper which provides asynchronous serialization and asynchronous write.
* This class takes an object instance, passes it to background writer thread (using Write Cache)
* where it is serialized and written to disk. Async write does not affect commit durability,
* write cache is flushed into disk on each commit. Modified records are held in small instance cache,
* until they are written into disk.
*
* This feature is enabled by default and can be disabled by calling {@link DBMaker#asyncWriteDisable()}.
* Write Cache is flushed in regular intervals or when it becomes full. Flush interval is 100 ms by default and
* can be controlled by {@link DBMaker#asyncWriteFlushDelay(int)}. Increasing this interval may improve performance
* in scenarios where frequently modified items should be cached, typically {@link BTreeMap} import where keys
* are presorted.
*
* Asynchronous write does not affect commit durability. Write Cache is flushed during each commit, rollback and close call.
* You may also flush Write Cache manually by using {@link org.mapdb.AsyncWriteEngine#clearCache()} method.
* There is global lock which prevents record being updated while commit is in progress.
*
* This wrapper starts one threads named `MapDB writer #N` (where N is static counter).
* Async Writer takes modified records from Write Cache and writes them into store.
* It also preallocates new recids, as finding empty `recids` takes time so small stash is pre-allocated.
* It runs as `daemon`, so it does not prevent JVM to exit.
*
* Asynchronous Writes have several advantages (especially for single threaded user). But there are two things
* user should be aware of:
*
* * Because data are serialized on back-ground thread, they need to be thread safe or better immutable.
* When you insert record into MapDB and modify it latter, this modification may happen before item
* was serialized and you may not be sure what version was persisted
*
* * Asynchronous writes have some overhead and introduce single bottle-neck. This usually not issue for
* single or two threads, but in multi-threaded environment it may decrease performance.
* So in truly concurrent environments with many updates (network servers, parallel computing )
* you should disable Asynchronous Writes.
*
*
* @see Engine
* @see EngineWrapper
*
* @author Jan Kotek
*
*
*
*/
public class AsyncWriteEngine extends EngineWrapper implements Engine {
/** ensures thread name is followed by number */
protected static final AtomicLong threadCounter = new AtomicLong();
/** used to signal that object was deleted*/
protected static final Object TOMBSTONE = new Object();
protected final long[] newRecids = new long[CC.ASYNC_RECID_PREALLOC_QUEUE_SIZE];
protected int newRecidsPos = 0;
protected final ReentrantLock newRecidsLock = new ReentrantLock(CC.FAIR_LOCKS);
/** Associates `recid` from Write Queue with record data and serializer. */
protected final LongConcurrentHashMap> writeCache
= new LongConcurrentHashMap>();
/** Each insert to Write Queue must hold read lock.
* Commit, rollback and close operations must hold write lock
*/
protected final ReentrantReadWriteLock commitLock = new ReentrantReadWriteLock(CC.FAIR_LOCKS);
/** number of active threads running, used to await thread termination on close */
protected final CountDownLatch activeThreadsCount = new CountDownLatch(1);
/** If background thread fails with exception, it is stored here, and rethrown to all callers.*/
protected volatile Throwable threadFailedException = null;
/** indicates that `close()` was called and background threads are being terminated*/
protected volatile boolean closeInProgress = false;
/** flush Write Queue every N milliseconds */
protected final int asyncFlushDelay;
protected final AtomicReference action = new AtomicReference(null);
/**
* Construct new class and starts background threads.
* User may provide executor in which background tasks will be executed,
* otherwise MapDB starts two daemon threads.
*
* @param engine which stores data.
* @param _asyncFlushDelay flush Write Queue every N milliseconds
* @param executor optional executor to run tasks. If null daemon threads will be created
*/
public AsyncWriteEngine(Engine engine, int _asyncFlushDelay,Executor executor) {
super(engine);
this.asyncFlushDelay = _asyncFlushDelay;
startThreads(executor);
}
public AsyncWriteEngine(Engine engine) {
this(engine, CC.ASYNC_WRITE_FLUSH_DELAY, null);
}
protected static final class WriterRunnable implements Runnable{
protected final WeakReference engineRef;
protected final long asyncFlushDelay;
public WriterRunnable(AsyncWriteEngine engine) {
this.engineRef = new WeakReference(engine);
this.asyncFlushDelay = engine.asyncFlushDelay;
}
@Override public void run() {
try{
//run in loop
for(;;){
//if conditions are right, slow down writes a bit
if(asyncFlushDelay!=0 ){
LockSupport.parkNanos(1000L * 1000L * asyncFlushDelay);
}
AsyncWriteEngine engine = engineRef.get();
if(engine==null) return; //stop thread if this engine has been GCed
if(engine.threadFailedException !=null) return; //other thread has failed, no reason to continue
if(!engine.runWriter()) return;
}
} catch (Throwable e) {
AsyncWriteEngine engine = engineRef.get();
if(engine!=null) engine.threadFailedException = e;
}finally {
AsyncWriteEngine engine = engineRef.get();
if(engine!=null) engine.activeThreadsCount.countDown();
}
}
}
/**
* Starts background threads.
* You may override this if you wish to start thread different way
*
* @param executor optional executor to run tasks, if null deamon threads will be created
*/
protected void startThreads(Executor executor) {
final Runnable writerRun = new WriterRunnable(this);
if(executor!=null){
executor.execute(writerRun);
return;
}
final long threadNum = threadCounter.incrementAndGet();
Thread writerThread = new Thread(writerRun,"MapDB writer #"+threadNum);
writerThread.setDaemon(true);
writerThread.start();
}
/** runs on background thread. Takes records from Write Queue, serializes and writes them.*/
protected boolean runWriter() throws InterruptedException {
final CountDownLatch latch = action.getAndSet(null);
int counter=0;
do{
LongMap.LongMapIterator> iter = writeCache.longMapIterator();
while(iter.moveToNext()){
//usual write
final long recid = iter.key();
Fun.Tuple2
© 2015 - 2025 Weber Informatics LLC | Privacy Policy