/* * Copyright (c) [2016] [ <ether.camp> ] * This file is part of the ethereumJ library. * * The ethereumJ library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ethereumJ library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ethereumJ library. If not, see <http://www.gnu.org/licenses/>. */ package org.ethereum.db; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.JdkFutureAdapters; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ThreadFactoryBuilder; import org.ethereum.config.CommonConfig; import org.ethereum.config.SystemProperties; import org.ethereum.datasource.AbstractCachedSource; import org.ethereum.datasource.AsyncFlushable; import org.ethereum.datasource.DbSource; import org.ethereum.datasource.WriteCache; import org.ethereum.listener.CompositeEthereumListener; import org.ethereum.listener.EthereumListenerAdapter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.concurrent.*; /** * Created by Anton Nashatyrev on 01.12.2016. */ public class DbFlushManager { private static final Logger logger = LoggerFactory.getLogger("db"); List<AbstractCachedSource<byte[], byte[]>> writeCaches = new ArrayList<>(); Set<DbSource> dbSources = new HashSet<>(); AbstractCachedSource<byte[], byte[]> stateDbCache; long sizeThreshold; int commitsCountThreshold; boolean syncDone = false; boolean flushAfterSyncDone; SystemProperties config; int commitCount = 0; private final BlockingQueue<Runnable> executorQueue = new ArrayBlockingQueue<>(1); private final ExecutorService flushThread = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, executorQueue, new ThreadFactoryBuilder().setNameFormat("DbFlushManagerThread-%d").build()); Future<Boolean> lastFlush = Futures.immediateFuture(false); public DbFlushManager(SystemProperties config, Set<DbSource> dbSources, AbstractCachedSource<byte[], byte[]> stateDbCache) { this.config = config; this.dbSources = dbSources; sizeThreshold = config.getConfig().getInt("cache.flush.writeCacheSize") * 1024 * 1024; commitsCountThreshold = config.getConfig().getInt("cache.flush.blocks"); flushAfterSyncDone = config.getConfig().getBoolean("cache.flush.shortSyncFlush"); this.stateDbCache = stateDbCache; } @Autowired public void setEthereumListener(CompositeEthereumListener listener) { if (!flushAfterSyncDone) return; listener.addListener(new EthereumListenerAdapter() { @Override public void onSyncDone(SyncState state) { if (state == SyncState.COMPLETE) { logger.info("DbFlushManager: long sync done, flushing each block now"); syncDone = true; } } }); } public void setSizeThreshold(long sizeThreshold) { this.sizeThreshold = sizeThreshold; } public void addCache(AbstractCachedSource<byte[], byte[]> cache) { writeCaches.add(cache); } public long getCacheSize() { long ret = 0; for (AbstractCachedSource<byte[], byte[]> writeCache : writeCaches) { ret += writeCache.estimateCacheSize(); } return ret; } public synchronized void commit(Runnable atomicUpdate) { atomicUpdate.run(); commit(); } public synchronized void commit() { long cacheSize = getCacheSize(); if (sizeThreshold >= 0 && cacheSize >= sizeThreshold) { logger.info("DbFlushManager: flushing db due to write cache size (" + cacheSize + ") reached threshold (" + sizeThreshold + ")"); flush(); } else if (commitsCountThreshold > 0 && commitCount >= commitsCountThreshold) { logger.info("DbFlushManager: flushing db due to commits (" + commitCount + ") reached threshold (" + commitsCountThreshold + ")"); flush(); commitCount = 0; } else if (flushAfterSyncDone && syncDone) { logger.debug("DbFlushManager: flushing db due to short sync"); flush(); } commitCount++; } public synchronized void flushSync() { try { flush().get(); } catch (Exception e) { throw new RuntimeException(e); } } public synchronized Future<Boolean> flush() { if (!lastFlush.isDone()) { logger.info("Waiting for previous flush to complete..."); try { lastFlush.get(); } catch (Exception e) { logger.error("Error during last flush", e); } } logger.debug("Flipping async storages"); for (AbstractCachedSource<byte[], byte[]> writeCache : writeCaches) { try { if (writeCache instanceof AsyncFlushable) { ((AsyncFlushable) writeCache).flipStorage(); } } catch (InterruptedException e) { throw new RuntimeException(e); } } logger.debug("Submitting flush task"); return lastFlush = flushThread.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { boolean ret = false; long s = System.nanoTime(); logger.info("Flush started"); for (AbstractCachedSource<byte[], byte[]> writeCache : writeCaches) { if (writeCache instanceof AsyncFlushable) { try { ret |= ((AsyncFlushable) writeCache).flushAsync().get(); } catch (InterruptedException e) { throw new RuntimeException(e); } } else { ret |= writeCache.flush(); } } if (stateDbCache != null) { logger.debug("Flushing to DB"); stateDbCache.flush(); } logger.info("Flush completed in " + (System.nanoTime() - s) / 1000000 + " ms"); return ret; } }); } /** * Flushes all caches and closes all databases */ public synchronized void close() { logger.info("Flushing DBs..."); flushSync(); logger.info("Flush done."); for (DbSource dbSource : dbSources) { logger.info("Closing DB: {}", dbSource.getName()); try { dbSource.close(); } catch (Exception ex) { logger.error(String.format("Caught error while closing DB: %s", dbSource.getName()), ex); } } } }