PipedInputStream和PipedOutputStream中的异常传播

时间:2015-11-13 05:23:28

标签: java multithreading

我有一个数据生成器,它在一个单独的线程中运行,并将生成的数据推送到PipedOutputStream,并连接到PipedInputStream。此输入流的引用通过公共API公开,以便任何客户端都可以使用它。 PipedInputStream包含有限的缓冲区,如果已满,则会阻止数据生成器。基本上,当客户端从输入流中读取数据时,数据生成器会生成新数据。

问题是数据生成器可能会失败并抛出异常。但是当消费者在一个单独的线程中运行时,没有很好的方法可以将异常传递给客户端。

我所做的是捕获该异常并关闭输入流。这将在客户端产生IOException消息“管道关闭”,但我真的想给客户提供背后的真正原因。

这是我的API的粗略代码:

public InputStream getData() {
    final PipedInputStream inputStream = new PipedInputStream(config.getPipeBufferSize());
    final PipedOutputStream outputStream = new PipedOutputStream(inputStream);

    Thread thread = new Thread(() -> {
        try {
          // Start producing the data and push it into output stream.
          // The production my fail and throw an Exception with the reason
        } catch (Exception e) {
            try {
                // What to do here?
                outputStream.close();
                inputStream.close();
            } catch (IOException e1) {
            }
        }
    });
    thread.start();

    return inputStream;
}

我有两个想法如何解决这个问题:

  1. 将异常存储在父对象中,并通过API将其公开给客户端。 I. e。如果读取失败并显示IOException,则客户端可以向API询问原因。
  2. 扩展/重新实现管道流,以便我可以将原因传递给close()方法。然后,流引发的IOException可以包含该原因作为消息。
  3. 有更好的想法吗?

2 个答案:

答案 0 :(得分:2)

巧合的是,我只是编写了类似的代码来允许GZip压缩流。您不需要扩展PipedInputStream,只需FilterInputStream即可执行并返回包装版本,例如

final PipedInputStream in = new PipedInputStream();
final InputStreamWithFinalExceptionCheck inWithException = new InputStreamWithFinalExceptionCheck(in);
final PipedOutputStream out = new PipedOutputStream(in);
Thread thread = new Thread(() -> {
    try {
      // Start producing the data and push it into output stream.
      // The production my fail and throw an Exception with the reason
    } catch (final IOException e) {
        inWithException.fail(e);
    } finally {
        inWithException.countDown();
    }
});
thread.start();
return inWithException;

然后InputStreamWithFinalExceptionCheck只是

private static final class InputStreamWithFinalExceptionCheck extends FilterInputStream {
    private final AtomicReference<IOException> exception = new AtomicReference<>(null);
    private final CountDownLatch complete = new CountDownLatch(1);

    public InputStreamWithFinalExceptionCheck(final InputStream stream) {
        super(stream);
    }

    @Override
    public void close() throws IOException {
        try {
            complete.await();
            final IOException e = exception.get();
            if (e != null) {
                throw e;
            }
        } catch (final InterruptedException e) {
            throw new IOException("Interrupted while waiting for synchronised closure");
        } finally {
            stream.close();
        }
    }

    public void fail(final IOException e) {
        exception.set(Preconditions.checkNotNull(e));
    }

    public void countDown() {complete.countDown();}
}

答案 1 :(得分:2)

这是我的实现,取自上面接受的答案https://stackoverflow.com/a/33698661/5165540,我不使用CountDownLatch records = list(SeqIO.parse("sys.argv[0]", "fasta")) records.sort(key=lambda x : x.id) SeqIO.write(records, "sys.arg[0]-sorted.fas", "fasta") 因为如果在编写完成之前InputStream突然关闭会导致死锁全部内容。 我仍然设置了在使用PipedOutpuStream时捕获的异常,并且我在生成线程中创建了PipedOutputStream,使用try-finally-resource模式来确保它被关闭,在供应商中等待直到2个流被传送。

complete.await()

调用代码就像

Supplier<InputStream> streamSupplier = new Supplier<InputStream>() {
        @Override
        public InputStream get() {
            final AtomicReference<IOException> osException = new AtomicReference<>();
            final CountDownLatch piped = new CountDownLatch(1);

            final PipedInputStream is = new PipedInputStream();

            FilterInputStream fis = new FilterInputStream(is) {
                @Override
                public void close() throws IOException {
                    try {
                        IOException e = osException.get();
                        if (e != null) {
                            //Exception thrown by the write will bubble up to InputStream reader
                            throw new IOException("IOException in writer", e);
                        }
                    } finally {
                        super.close();
                    }
                };
            };

            Thread t = new Thread(() -> {
                    try (PipedOutputStream os = new PipedOutputStream(is)) {
                        piped.countDown();
                        writeIozToStream(os, projectFile, dataFolder);
                    } catch (final IOException e) {
                        osException.set(e);
                    }
            });
            t.start();

            try {
                piped.await();
            } catch (InterruptedException e) {
                t.cancel();
                Thread.currentThread().interrupt();
            }

            return fis;
        }
    };

因此,当InputStream关闭时,将在PipedOutputStream中发出信号,最终导致“管道关闭”IOException,此时将被忽略。

如果我保留FilterInputStream try (InputStream is = streamSupplier.getInputStream()) { //Read stream in full } 中的complete.await()行,我可能会遇到死锁(PipedInputStream试图关闭,等待close(),而PipedOutputStream在PipedInputStream上永远等待{{} 1}})