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); } } } }