package htsjdk.tribble.readers;
import htsjdk.samtools.util.CloserUtil;
import htsjdk.tribble.TribbleException;
import java.io.Reader;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* A LineReader implementation that delegates the work of reading and fetching lines to another thread. The thread terminates when it
* encounters EOF in the underlying reader, or when this LineReader is closed.
*
* @author mccowan
*/
public class AsynchronousLineReader implements LineReader {
public static final int DEFAULT_NUMBER_LINES_BUFFER = 100;
private final LongLineBufferedReader bufferedReader;
private final BlockingQueue<String> lineQueue;
private final Thread worker;
private volatile Throwable workerException = null;
private volatile boolean eofReached = false;
public AsynchronousLineReader(final Reader reader, final int lineReadAheadSize) {
bufferedReader = new LongLineBufferedReader(reader);
lineQueue = new LinkedBlockingQueue<String>(lineReadAheadSize);
worker = new Thread(new Worker());
worker.setDaemon(true);
worker.start();
}
public AsynchronousLineReader(final Reader reader) {
this(reader, DEFAULT_NUMBER_LINES_BUFFER);
}
@Override
public String readLine() {
try {
// Continually poll until we get a result, unless the underlying reader is finished.
for (; ; ) {
checkAndThrowIfWorkerException();
final String pollResult = this.lineQueue.poll(100, TimeUnit.MILLISECONDS); // Not ideal for small files.
if (pollResult == null) {
if (eofReached) {
checkAndThrowIfWorkerException();
return lineQueue.poll(); // If there is nothing left, returns null as expected. Otherwise, grabs next element.
}
} else {
return pollResult;
}
}
} catch (final InterruptedException e) {
throw new TribbleException("Line polling interrupted.", e);
}
}
private void checkAndThrowIfWorkerException() {
if (workerException != null) {
throw new TribbleException("Exception encountered in worker thread.", workerException);
}
}
@Override
public void close() {
this.worker.interrupt(); // Allow the worker to close gracefully.
}
private class Worker implements Runnable {
@Override
public void run() {
try {
for (; ; ) {
final String line = bufferedReader.readLine();
if (line == null) {
eofReached = true;
break;
} else {
try {
lineQueue.put(line);
} catch (final InterruptedException e) {
/**
* A thread interruption is not an exceptional state: it means a {@link AsynchronousLineReader#close();} has
* been called, so shut down gracefully.
*/
break;
}
}
}
} catch (final Throwable e) {
AsynchronousLineReader.this.workerException = e;
} finally {
CloserUtil.close(AsynchronousLineReader.this.bufferedReader);
}
}
}
}