package htsjdk.samtools.util;
import java.io.Closeable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
/**
* Abstract class that is designed to be extended and specialized to provide an asynchronous
* wrapper around any kind of Writer class that takes an object and writes it out somehow.
*
* @author Tim Fennell
*/
public abstract class AbstractAsyncWriter<T> implements Closeable {
private static volatile int threadsCreated = 0; // Just used for thread naming.
public static final int DEFAULT_QUEUE_SIZE = 2000;
private final AtomicBoolean isClosed = new AtomicBoolean(false);
private final BlockingQueue<T> queue;
private final Thread writer;
private final WriterRunnable writerRunnable;
private final AtomicReference<Throwable> ex = new AtomicReference<Throwable>(null);
/** Returns the prefix to use when naming threads. */
protected abstract String getThreadNamePrefix();
protected abstract void synchronouslyWrite(final T item);
protected abstract void synchronouslyClose();
/**
* Creates an AbstractAsyncWriter that will use the provided WriterRunnable to consume from the
* internal queue and write records into the synchronous writer.
*/
protected AbstractAsyncWriter(final int queueSize) {
this.queue = new ArrayBlockingQueue<T>(queueSize);
this.writerRunnable = new WriterRunnable();
this.writer = new Thread(writerRunnable, getThreadNamePrefix() + threadsCreated++);
this.writer.setDaemon(true);
this.writer.start();
}
/**
* Public method for sub-classes or ultimately consumers to put an item into the queue
* to be written out.
*/
public void write(final T item) {
if (this.isClosed.get()) throw new RuntimeException("Attempt to add record to closed writer.");
checkAndRethrow();
try { this.queue.put(item); }
catch (final InterruptedException ie) { throw new RuntimeException("Interrupted queueing item for writing.", ie); }
checkAndRethrow();
}
/**
* Attempts to finishing draining the queue and then calls synchronoslyClose() to allow implementation
* to do any one time clean up.
*/
public void close() {
checkAndRethrow();
if (!this.isClosed.getAndSet(true)) {
try { this.writer.join(); }
catch (final InterruptedException ie) { throw new RuntimeException("Interrupted waiting on writer thread.", ie); }
// Assert that the queue is empty
if (!this.queue.isEmpty()) {
throw new RuntimeException("Queue should be empty but is size: " + this.queue.size());
}
synchronouslyClose();
checkAndRethrow();
}
}
/**
* Checks to see if an exception has been raised in the writer thread and if so rethrows it as an Error
* or RuntimeException as appropriate.
*/
private final void checkAndRethrow() {
final Throwable t = this.ex.get();
if (t != null) {
if (t instanceof Error) throw (Error) t;
if (t instanceof RuntimeException) throw (RuntimeException) t;
else throw new RuntimeException(t);
}
}
/**
* Small Runnable implementation that simply reads from the blocking queue and writes to the
* synchronous writer.
*/
private class WriterRunnable implements Runnable {
public void run() {
try {
while (!queue.isEmpty() || !isClosed.get()) {
try {
final T item = queue.poll(2, TimeUnit.SECONDS);
if (item != null) synchronouslyWrite(item);
}
catch (final InterruptedException ie) {
/* Do Nothing */
}
}
}
catch (final Throwable t) {
ex.compareAndSet(null, t);
// In case a writer was blocking on a full queue before ex has been set, clear the queue
// so that the writer will no longer be blocked so that it can see the exception.
queue.clear();
}
}
}
}