package org.ethereum.datasource;
import com.google.common.util.concurrent.*;
import org.apache.commons.lang3.concurrent.ConcurrentUtils;
import org.ethereum.util.ALock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Created by Anton Nashatyrev on 18.01.2017.
*/
public abstract class AsyncWriteCache<Key, Value> extends AbstractCachedSource<Key, Value> implements AsyncFlushable {
private static final Logger logger = LoggerFactory.getLogger("db");
private static ListeningExecutorService flushExecutor = MoreExecutors.listeningDecorator(
Executors.newFixedThreadPool(2, new ThreadFactoryBuilder().setNameFormat("AsyncWriteCacheThread-%d").build()));
protected volatile WriteCache<Key, Value> curCache;
protected WriteCache<Key, Value> flushingCache;
private ListenableFuture<Boolean> lastFlush = Futures.immediateFuture(false);
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final ALock rLock = new ALock(rwLock.readLock());
private final ALock wLock = new ALock(rwLock.writeLock());
private String name = "<null>";
public AsyncWriteCache(Source<Key, Value> source) {
super(source);
flushingCache = createCache(source);
flushingCache.setFlushSource(true);
curCache = createCache(flushingCache);
}
protected abstract WriteCache<Key, Value> createCache(Source<Key, Value> source);
@Override
public Collection<Key> getModified() {
try (ALock l = rLock.lock()) {
return curCache.getModified();
}
}
@Override
public boolean hasModified() {
try (ALock l = rLock.lock()) {
return curCache.hasModified();
}
}
@Override
public void put(Key key, Value val) {
try (ALock l = rLock.lock()) {
curCache.put(key, val);
}
}
@Override
public void delete(Key key) {
try (ALock l = rLock.lock()) {
curCache.delete(key);
}
}
@Override
public Value get(Key key) {
try (ALock l = rLock.lock()) {
return curCache.get(key);
}
}
@Override
public synchronized boolean flush() {
try {
flipStorage();
flushAsync();
return flushingCache.hasModified();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
@Override
Entry<Value> getCached(Key key) {
return curCache.getCached(key);
}
@Override
public synchronized void flipStorage() throws InterruptedException {
// if previous flush still running
try {
if (!lastFlush.isDone()) logger.debug("AsyncWriteCache (" + name + "): waiting for previous flush to complete");
lastFlush.get();
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
try (ALock l = wLock.lock()) {
flushingCache.cache = curCache.cache;
curCache = createCache(flushingCache);
}
}
public synchronized ListenableFuture<Boolean> flushAsync() throws InterruptedException {
logger.debug("AsyncWriteCache (" + name + "): flush submitted");
lastFlush = flushExecutor.submit(new Callable<Boolean>() {
@Override
public Boolean call() {
logger.debug("AsyncWriteCache (" + name + "): flush started");
long s = System.currentTimeMillis();
boolean ret = flushingCache.flush();
logger.debug("AsyncWriteCache (" + name + "): flush completed in " + (System.currentTimeMillis() - s) + " ms");
return ret;
}
});
return lastFlush;
}
@Override
public long estimateCacheSize() {
// 2.0 is upper cache size estimation to take into account there are two
// caches may exist simultaneously up to doubling cache size
return (long) (curCache.estimateCacheSize() * 2.0);
}
@Override
protected synchronized boolean flushImpl() {
return false;
}
public AsyncWriteCache<Key, Value> withName(String name) {
this.name = name;
return this;
}
}