package be.bagofwords.db.cached; import be.bagofwords.application.BowTaskScheduler; import be.bagofwords.application.memory.MemoryGobbler; import be.bagofwords.application.memory.MemoryManager; import be.bagofwords.application.memory.MemoryStatus; import be.bagofwords.cache.CachesManager; import be.bagofwords.cache.DynamicMap; import be.bagofwords.cache.ReadCache; import be.bagofwords.db.DataInterface; import be.bagofwords.db.LayeredDataInterface; import be.bagofwords.iterator.CloseableIterator; import be.bagofwords.ui.UI; import be.bagofwords.util.KeyValue; import be.bagofwords.util.SafeThread; import be.bagofwords.util.Utils; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; public class CachedDataInterface<T extends Object> extends LayeredDataInterface<T> implements MemoryGobbler { private static final int TIME_BETWEEN_FLUSHES_WRITE_BUFFER = 1000; private static final int NUM_OF_WRITE_BUFFERS = 10; private ReadCache<T> readCache; private boolean readCacheDirty; private List<SwappableDynamicMap> writeBuffers; private final MemoryManager memoryManager; private final SafeThread initializeCachesThread; private long timeOfLastFlushOfWriteBuffer; public CachedDataInterface(MemoryManager memoryManager, CachesManager cachesManager, DataInterface<T> baseInterface, BowTaskScheduler taskScheduler) { super(baseInterface); this.memoryManager = memoryManager; this.memoryManager.registerMemoryGobbler(this); this.readCache = cachesManager.createNewCache(getName(), baseInterface.getObjectClass()); this.readCacheDirty = false; this.writeBuffers = new ArrayList<>(); for (int i = 0; i < NUM_OF_WRITE_BUFFERS; i++) { this.writeBuffers.add(new SwappableDynamicMap()); } this.initializeCachesThread = new InitializeCachesThread(baseInterface); this.initializeCachesThread.start(); taskScheduler.schedulePeriodicTask(() -> ifNotClosed(this::flushWriteBuffer), TIME_BETWEEN_FLUSHES_WRITE_BUFFER); timeOfLastFlushOfWriteBuffer = System.currentTimeMillis(); } @Override public T read(long key) { KeyValue<T> cachedValue = readCache.get(key); if (cachedValue == null) { //never read, read from direct T value = baseInterface.read(key); readCache.put(key, value); return value; } else { return cachedValue.getValue(); } } @Override public boolean mightContain(long key) { KeyValue<T> cachedValue = readCache.get(key); if (cachedValue != null) { if (cachedValue.getValue() == null) { return false; } else { return true; } } else { return baseInterface.mightContain(key); } } @Override public void write(long key, T value) { checkWriteConditions(); unsafeWrite(key, value); } private void checkWriteConditions() { memoryManager.waitForSufficientMemory(); waitForSlowFlushes(); } private void unsafeWrite(long key, T value) { int writeBufferInd = (int) (key % NUM_OF_WRITE_BUFFERS); if (writeBufferInd < 0) { writeBufferInd += NUM_OF_WRITE_BUFFERS; } SwappableDynamicMap writeBuffer = writeBuffers.get(writeBufferInd); synchronized (writeBuffer) { KeyValue<T> cachedValue = writeBuffer.getMap().get(key); if (cachedValue == null) { //first write of this key writeBuffer.getMap().put(key, value); } else { if (value != null && cachedValue.getValue() != null) { T combinedValue = getCombinator().combine(cachedValue.getValue(), value); writeBuffer.getMap().put(key, combinedValue); } else { writeBuffer.getMap().put(key, value); } } } } private void waitForSlowFlushes() { while (System.currentTimeMillis() - timeOfLastFlushOfWriteBuffer > TIME_BETWEEN_FLUSHES_WRITE_BUFFER * 10) { //exceptionally long time since last flush, let's wait for the flush to finish Utils.threadSleep(10); } } @Override public CloseableIterator<KeyValue<T>> cachedValueIterator() { final Iterator<KeyValue<T>> iterator = readCache.iterator(); return new CloseableIterator<KeyValue<T>>() { @Override protected void closeInt() { //ok } @Override public boolean hasNext() { return iterator.hasNext(); } @Override public KeyValue<T> next() { return iterator.next(); } }; } @Override public void write(Iterator<KeyValue<T>> entries) { int numOfEntriesWritten = 0; while (entries.hasNext()) { if (numOfEntriesWritten % 100 == 0) { checkWriteConditions(); } KeyValue<T> next = entries.next(); write(next.getKey(), next.getValue()); numOfEntriesWritten++; } } @Override public synchronized void doCloseImpl() { try { stopInitializeCachesThread(); flush(); } finally { //even if the flush failed, we remove our data structures readCache.clear(); readCache = null; writeBuffers = null; baseInterface.close(); } } public synchronized void flush() { flushWriteBuffer(); baseInterface.flush(); cleanDirtyReadCache(); } private void cleanDirtyReadCache() { if (readCacheDirty) { stopInitializeCachesThread(); readCacheDirty = false; //should come before clearing read cache readCache.clear(); } } private synchronized long flushWriteBuffer() { //flush values in write cache long valuesRemoved = writeBuffers.parallelStream().collect(Collectors.summingLong( buffer -> { DynamicMap<T> oldValues; synchronized (buffer) { oldValues = buffer.putNew(); } if (oldValues.size() > 0) { baseInterface.write(oldValues.iterator()); readCacheDirty = true; //should come after writing values } return oldValues.size(); } )); timeOfLastFlushOfWriteBuffer = System.currentTimeMillis(); return valuesRemoved; } @Override public void dropAllData() { stopInitializeCachesThread(); for (SwappableDynamicMap writeBuffer : writeBuffers) { synchronized (writeBuffer) { writeBuffer.putNew(); } } readCache.clear(); baseInterface.dropAllData(); } private void stopInitializeCachesThread() { if (!initializeCachesThread.isFinished()) { initializeCachesThread.terminate(); initializeCachesThread.waitForFinish(); } } @Override public long freeMemory() { return flushWriteBuffer(); } @Override public long getMemoryUsage() { return sizeOfWriteBuffers(); } private long sizeOfWriteBuffers() { long result = 0; for (SwappableDynamicMap writeBuffer : writeBuffers) { synchronized (writeBuffer) { result += writeBuffer.getMap().size(); } } return result; } private class InitializeCachesThread extends SafeThread { public InitializeCachesThread(DataInterface<T> baseInterface) { super("initialize_cache_" + baseInterface.getName(), false); } @Override protected void runInt() throws Exception { CloseableIterator<KeyValue<T>> iterator = baseInterface.cachedValueIterator(); int numOfValuesWritten = 0; long start = System.currentTimeMillis(); while (iterator.hasNext() && memoryManager.getMemoryStatus() == MemoryStatus.FREE && !isTerminateRequested()) { KeyValue<T> next = iterator.next(); readCache.put(next.getKey(), next.getValue()); numOfValuesWritten++; } if (iterator.hasNext()) { UI.write("Could not add (all) values to cache of " + baseInterface.getName() + " because memory was full"); } /*else { UI.write("Added " + numOfValuesWritten + " values to cache of " + baseInterface.getName() + " in " + (System.currentTimeMillis() - start) + " ms"); }*/ iterator.close(); } } private class SwappableDynamicMap { private DynamicMap<T> map; private SwappableDynamicMap() { map = new DynamicMap<>(getObjectClass()); } public DynamicMap<T> putNew() { DynamicMap<T> old = map; map = new DynamicMap<>(getObjectClass()); return old; } public DynamicMap<T> getMap() { return map; } } }