package com.netflix.astyanax.contrib.dualwrites; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.atomic.AtomicBoolean; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Class that acts as an async logger that helps 'unblock' the caller immediately. * It hands off to a {@link LinkedBlockingQueue} and there is a thread on the other end * that polls the queue for tasks which are then actually written using a real logger * provided to this class. * * It leverages the decorator pattern to do this. * * @author poberai * */ public class AsyncFailedWritesLogger implements FailedWritesLogger { private static final Logger Logger = LoggerFactory.getLogger(AsyncFailedWritesLogger.class); private static final int DEFAULT_QUEUE_SIZE = 1000; private final FailedWritesLogger actualWriter; private ExecutorService threadPool; private final LinkedBlockingQueue<WriteMetadata> taskQueue; private final AtomicBoolean stop = new AtomicBoolean(false); public AsyncFailedWritesLogger(FailedWritesLogger writer) { this(writer, DEFAULT_QUEUE_SIZE); } public AsyncFailedWritesLogger(FailedWritesLogger writer, int queueSize) { this.actualWriter = writer; this.taskQueue = new LinkedBlockingQueue<WriteMetadata>(queueSize); } @Override public void logFailedWrite(WriteMetadata failedWrite) { boolean success = taskQueue.offer(failedWrite); if (!success) { Logger.error("Async failed writes logger is backed up and is dropping failed writes " + failedWrite); } } @Override public void init() { if (stop.get()) { Logger.error("Will not start async logger, already stopped"); return; } if (threadPool == null) { threadPool = Executors.newScheduledThreadPool(1); } threadPool.submit(new Callable<Void>() { @Override public Void call() throws Exception { Logger.info("Async failed writes logger starting.."); while (!stop.get() && !Thread.currentThread().isInterrupted()) { try { WriteMetadata writeMD = taskQueue.take(); // this is a blocking call try { actualWriter.logFailedWrite(writeMD); } catch (Exception e) { Logger.error("Failed to log failed write asynchronously", e); } } catch(InterruptedException e) { // stop blocking on the queue and exit stop.set(true); } } Logger.info("Async failed writes logger exiting.."); return null; } }); } @Override public void shutdown() { stop.set(true); if (threadPool != null) { threadPool.shutdownNow(); } } }