package com.jivesoftware.os.amza.service.replication; import com.jivesoftware.os.amza.api.FailedToAchieveQuorumException; import com.jivesoftware.os.amza.api.partition.Durability; import com.jivesoftware.os.amza.service.take.HighwaterStorage; import com.jivesoftware.os.mlogger.core.MetricLogger; import com.jivesoftware.os.mlogger.core.MetricLoggerFactory; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; /** * */ public final class AsyncStripeFlusher implements Runnable { private static final MetricLogger LOG = MetricLoggerFactory.getLogger(); private final int id; private final AtomicBoolean running = new AtomicBoolean(false); private final AtomicLong asyncVersion = new AtomicLong(0); private final AtomicLong forceVersion = new AtomicLong(0); private final AtomicLong asyncFlushedToVersion = new AtomicLong(0); private final AtomicLong forceFlushedToVersion = new AtomicLong(0); private final Object force = new Object(); private final long asyncFlushIntervalMillis; private final Callable<Void> flushDelta; private final AtomicReference<HighwaterStorage> highwaterStorage = new AtomicReference<>(); public AsyncStripeFlusher(int id, long asyncFlushIntervalMillis, Callable<Void> flushDelta) { this.id = id; this.asyncFlushIntervalMillis = asyncFlushIntervalMillis; this.flushDelta = flushDelta; } public void forceFlush(Durability durability, long waitForFlushInMillis) throws Exception { if (durability == Durability.ephemeral || durability == Durability.fsync_never) { return; } long waitForVersion = 0; AtomicLong flushedToVersion; if (durability == Durability.fsync_async) { waitForVersion = asyncVersion.incrementAndGet(); flushedToVersion = asyncFlushedToVersion; } else if (durability == Durability.fsync_always) { waitForVersion = forceVersion.incrementAndGet(); flushedToVersion = forceFlushedToVersion; synchronized (force) { force.notifyAll(); } if (waitForFlushInMillis > 0) { long end = System.currentTimeMillis() + waitForFlushInMillis; while (waitForVersion > flushedToVersion.get()) { synchronized (flushedToVersion) { flushedToVersion.wait(Math.max(0, end - System.currentTimeMillis())); } if (end < System.currentTimeMillis()) { throw new FailedToAchieveQuorumException("We couldn't fsync within " + waitForFlushInMillis + " millis."); } } } } else { LOG.warn("Unsupported force flush for durability {}", durability); } } public void start(ExecutorService flusherExecutor, HighwaterStorage highwaterStorage) { if (running.compareAndSet(false, true)) { this.highwaterStorage.set(highwaterStorage); flusherExecutor.submit(this); } } public void stop() { running.compareAndSet(true, false); } @Override public void run() { try { long lastAsyncV = 0; long lastForcedV = 0; while (running.get()) { long asyncV = asyncVersion.get(); long forcedV = forceVersion.get(); if (lastAsyncV != asyncV || lastForcedV != forcedV) { try { flush(); lastAsyncV = asyncV; lastForcedV = forcedV; } catch (Throwable t) { LOG.error("Excountered the following while flushing.", t); } } if (lastAsyncV != asyncV) { asyncFlushedToVersion.set(asyncV); synchronized (asyncFlushedToVersion) { asyncFlushedToVersion.notifyAll(); } } if (lastForcedV != forcedV) { forceFlushedToVersion.set(forcedV); synchronized (forceFlushedToVersion) { forceFlushedToVersion.notifyAll(); } } synchronized (force) { if (forceVersion.get() == forcedV) { try { force.wait(asyncFlushIntervalMillis); } catch (InterruptedException ex) { LOG.warn("Async flusher for {} was interrupted.", id); return; } } } } } finally { running.set(false); } } private void flush() throws Exception { if (!highwaterStorage.get().flush(id, false, flushDelta)) { if (flushDelta != null) { flushDelta.call(); } } } }