Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* The MIT License
*
* Copyright (c) 2016 Daniel Cameron
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package htsjdk.samtools.util;
import htsjdk.samtools.Defaults;
import htsjdk.samtools.seekablestream.SeekableStream;
import htsjdk.samtools.util.zip.InflaterFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadFactory;
/**
* Asynchronous read-ahead implementation of {@link htsjdk.samtools.util.BlockCompressedInputStream}.
*
* Note that this implementation is not synchronized. If multiple threads access an instance concurrently, it must be synchronized externally.
*/
public class AsyncBlockCompressedInputStream extends BlockCompressedInputStream {
private static final int READ_AHEAD_BUFFERS = (int)Math.ceil((double) Defaults.NON_ZERO_BUFFER_SIZE / BlockCompressedStreamConstants.MAX_COMPRESSED_BLOCK_SIZE);
private static final Executor threadpool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(),new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setDaemon(true);
return t;
}
});
/**
* Next blocks (in stream order) that have already been decompressed.
*/
private final BlockingQueue mResult = new ArrayBlockingQueue<>(READ_AHEAD_BUFFERS);
/**
* Buffers used to decompress previous blocks that are no longer in use.
* These buffers are reused if possible.
* Note that no blocking occurs on this buffer and a blocking queue is used purely
* because it is a base library synchronized queue implementation
* (and Collections.synchronizedQueue() does not exist).
*/
private final BlockingQueue freeBuffers = new ArrayBlockingQueue<>(READ_AHEAD_BUFFERS);
/**
* Indicates whether a read-ahead task has been scheduled to run. Only one read-ahead task
* per stream can be scheduled at any one time.
*/
private final Semaphore running = new Semaphore(1);
/**
* Indicates whether any scheduled task should abort processing and terminate
* as soon as possible since the result will be discarded anyway.
*/
private volatile boolean mAbort = false;
public AsyncBlockCompressedInputStream(final InputStream stream) {
super(stream, true);
}
public AsyncBlockCompressedInputStream(final InputStream stream, InflaterFactory inflaterFactory) {
super(stream, true, inflaterFactory);
}
public AsyncBlockCompressedInputStream(final File file)
throws IOException {
super(file);
}
public AsyncBlockCompressedInputStream(final File file, InflaterFactory inflaterFactory)
throws IOException {
super(file, inflaterFactory);
}
public AsyncBlockCompressedInputStream(final URL url) {
super(url);
}
public AsyncBlockCompressedInputStream(final URL url, InflaterFactory inflaterFactory) {
super(url, inflaterFactory);
}
public AsyncBlockCompressedInputStream(final SeekableStream strm) {
super(strm);
}
public AsyncBlockCompressedInputStream(final SeekableStream strm, InflaterFactory inflaterFactory) {
super(strm, inflaterFactory);
}
@Override
protected DecompressedBlock nextBlock(byte[] bufferAvailableForReuse) {
if (bufferAvailableForReuse != null) {
freeBuffers.offer(bufferAvailableForReuse);
}
return nextBlockSync();
}
@Override
protected void prepareForSeek() {
flushReadAhead();
super.prepareForSeek();
}
@Override
public void close() throws IOException {
// Suppress interrupts while we close.
final boolean isInterrupted = Thread.interrupted();
mAbort = true;
try {
flushReadAhead();
super.close();
} finally {
if (isInterrupted) Thread.currentThread().interrupt();
}
}
/**
* Foreground thread blocking operation that aborts all read-ahead tasks
* and flushes all read-ahead results.
*/
private void flushReadAhead() {
final boolean abortStatus = mAbort;
mAbort = true;
try {
// block until the thread pool operation has completed
running.acquire();
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted waiting for decompression thread", e);
}
// flush any read-ahead results
mResult.clear();
mAbort = abortStatus;
running.release();
}
/**
* Ensures that a read-ahead task for this stream exists in the thread pool.
*/
private void ensureReadAhead() {
if (running.tryAcquire()) {
tryQueueTask();
}
}
/**
* Try to queue another read-ahead buffer
* This method should only be invoked by the owner of the running semaphore
*/
private void tryQueueTask() {
if (mAbort) {
// Potential deadlock between getNextBlock() and flushReadAhead() here
// This requires seek()/close() and another method to be called
// at the same time. Since the parent class is not thread-safe
// this is an acceptable behavior.
running.release();
return;
}
if (mResult.remainingCapacity() == 0) {
// read-ahead has already filled the results buffer
running.release();
if (mResult.remainingCapacity() > 0) {
// race condition this second check fixes:
// - worker thread context switch after checking remaining capacity is zero
// - foreground thread calls getNextBlock() repeatedly until blocking
// - worker thread switches back in and releases mutex
// = foreground blocking on mResult.take(), mutex free, no worker
// -> try to take back mutex and start worker
// if that fails, the someone else took the lock and would
// have started the background worker. (except if flushReadAhead()
// took the lock with getNextBlock() still blocking: not thread-safe
// so we don't care)
ensureReadAhead();
return;
} else {
return;
}
}
// we are able to perform a read-ahead operation
// ownership of the running mutex is now with the threadpool task
threadpool.execute(new AsyncBlockCompressedInputStreamRunnable());
}
/**
* Foreground thread blocking operation that retrieves the next read-ahead buffer.
* Lazy initiation of read-ahead is performed if required.
* @return next decompressed block in input stream
*/
private DecompressedBlock nextBlockSync() {
ensureReadAhead();
DecompressedBlock nextBlock;
try {
nextBlock = mResult.take();
} catch (InterruptedException e) {
return new DecompressedBlock(0, 0, e);
}
ensureReadAhead();
return nextBlock;
}
private class AsyncBlockCompressedInputStreamRunnable implements Runnable {
/**
* Thread pool operation that fills the read-ahead queue
*/
@Override
public void run() {
final DecompressedBlock decompressed = processNextBlock(freeBuffers.poll());
if (!mResult.offer(decompressed)) {
// offer should never block since we never queue a task when the results buffer is full
running.release(); // safety release to ensure foreground close() does not block indefinitely
throw new IllegalStateException("Decompression buffer full");
}
tryQueueTask();
}
}
}