/* * Copyright 2013-2016 EMC Corporation. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at * * http://www.apache.org/licenses/LICENSE-2.0.txt * * or in the "license" file accompanying this file. This file is distributed * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either * express or implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.emc.ecs.sync.util; import com.emc.object.util.ProgressInputStream; import com.emc.object.util.ProgressListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; public class ParallelInputStream extends FilterInputStream { private static final Logger log = LoggerFactory.getLogger(ParallelInputStream.class); ParallelReader reader; Thread readerThread; public ParallelInputStream(InputStream source, int bufferSize) throws IOException { this(source, bufferSize, null); } public ParallelInputStream(InputStream source, int bufferSize, ProgressListener sourceListener) throws IOException { super(null); // create a piped stream (ring buffer) -- the readerThread will be the write side and calling code // will be the read side PipedInputStream pin = new PipedInputStream(bufferSize); PipedOutputStream pout = new PipedOutputStream(pin); in = pin; if (sourceListener != null) source = new ProgressInputStream(source, sourceListener); reader = new ParallelReader(source, pout); readerThread = new Thread(reader); readerThread.start(); } @Override public int read() throws IOException { checkReader(); int result = super.read(); checkReader(); // also check after read in case we were blocked return result; } @Override public int read(byte[] b) throws IOException { return read(b, 0, b.length); } @Override public int read(byte[] b, int off, int len) throws IOException { checkReader(); int result = super.read(b, off, len); checkReader(); // also check after read in case we were blocked return result; } private void checkReader() throws IOException { if (reader.isFailed()) throw new IOException("reader failed", reader.getError()); } @Override public void close() throws IOException { try { super.close(); } finally { // if the reader is active, closing the pipe (above) will throw an exception in PipedOutputStream.write // if it is waiting for buffer space however, it will need to be interrupted or it will be frozen indefinitely if (!reader.isComplete() && !reader.isFailed()) readerThread.interrupt(); // if the reader is complete, this does nothing; if close was called early, this will wait until the reader // thread is notified of the close (an IOException will be thrown from PipedOutputStream.write) try { readerThread.join(); } catch (Throwable t) { log.warn("could not join reader thread", t); } } } class ParallelReader implements Runnable { private InputStream in; private OutputStream out; private volatile boolean complete = false; private volatile boolean failed = false; private volatile Throwable error; ParallelReader(InputStream in, OutputStream out) { this.in = in; this.out = out; } @Override public synchronized void run() { try (OutputStream outputStream = out) { // make sure the write-side of the piped stream is always closed byte[] chunk = new byte[128 * 1024]; int read; while ((read = in.read(chunk)) != -1) { outputStream.write(chunk, 0, read); } complete = true; } catch (Throwable t) { failed = true; error = t; } } public boolean isComplete() { return complete; } public boolean isFailed() { return failed; } public Throwable getError() { return error; } } }