Java PipedInputStream和PipedOutStream中的异常传播

Java PipedInputStream和PipedOutStream中的异常传播,java,multithreading,Java,Multithreading,我有一个数据生成器,它在单独的线程中运行,并将生成的数据推送到连接到PipedInputStream的pipedOutStream中。此输入流的引用通过公共API公开,以便任何客户端都可以使用它。PipedInputStream包含一个有限的缓冲区,如果缓冲区已满,将阻塞数据生成器。基本上,当客户机从输入流读取数据时,数据生成器生成新数据 问题是数据生产者可能会失败并引发异常。但是,由于消费者是在一个单独的线程中运行的,因此没有很好的方法将异常发送给客户机 我所做的是捕获该异常并关闭输入流。这将

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

问题是数据生产者可能会失败并引发异常。但是,由于消费者是在一个单独的线程中运行的,因此没有很好的方法将异常发送给客户机

我所做的是捕获该异常并关闭输入流。这将导致客户端出现带有消息“Pipe closed”的
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;
}
我有两个解决方法:

  • 将异常存储在父对象中,并通过API将其公开给客户端。即。如果读取失败并出现
    IOException
    ,客户机可以向API询问原因
  • 扩展/重新实现管道流,以便将原因传递给
    close()
    方法。然后流抛出的
    IOException
    可以将该原因作为消息包含

  • 有更好的主意吗?

    巧合的是,我刚刚编写了类似的代码,允许对流进行GZip压缩。您不需要扩展PipedInputStream,只需要扩展并返回一个包装版本,例如

    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();}
    }
    
    私有静态最终类InputStreamWithFinalExceptionCheck扩展FilterInputStream{
    private final AtomicReference exception=新的AtomicReference(null);
    私有最终倒计时闩锁完成=新倒计时闩锁(1);
    带有FinalExceptionCheck的公共输入流(最终输入流){
    超级(流);
    }
    @凌驾
    public void close()引发IOException{
    试一试{
    完成。等待();
    final IOException e=exception.get();
    如果(e!=null){
    投掷e;
    }
    }捕获(最终中断异常e){
    抛出新IOException(“在等待同步关闭时中断”);
    }最后{
    stream.close();
    }
    }
    公共作废失败(最终IOE例外){
    exception.set(premissions.checkNotNull(e));
    }
    public void countDown(){complete.countDown();}
    }
    
    这是我的实现,取自上述公认的答案,其中我不使用CountDownLatch
    complete.await()
    ,因为如果在编写器完成完整内容之前突然关闭InputStream,将导致死锁。 我仍然设置了在使用PipedOutpuStream时捕获的异常,并且我在spawn线程中创建了PipedOutputStream,使用try finally资源模式确保它被关闭,并在供应商中等待,直到这两个流通过管道传输

    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;
            }
        };
    
    因此,当is InputStream关闭时,将在PipeDoutpStream中发出信号,最终导致“Pipe closed”IOException,在该点被忽略


    如果我保留FilterInputStream
    close()
    中的
    complete.await()
    行,我可能会遇到死锁(PipedInputStream试图关闭,等待
    complete.await()
    ,而PipedOutStream永远在PipedInputStream
    awaitSpace上等待)

    如果扩展FilterInputStream,则可以使这一过程简单得多。这个解决方案对我来说很有效,但值得注意的是,两个流都必须显式关闭(这不在上面的代码中,但我想这是一种礼貌的暗示:)。在我的例子中,数据由多个并行作业生成,最后一个作业关闭输出流。如果失败,则必须在finally块中完成,否则将抛出“Write end dead”。对于输入流,
    close()
    引发异常。我在JUnit测试中使用了
    IOUtils.toString()
    ,它没有正确地关闭流,因此不会引发异常。为了避免死锁,还可以使用wait with timeout。在您的情况下,可以在writeIozToStream引发异常之前关闭InputStream,并且不会检查此异常。
    try (InputStream is = streamSupplier.getInputStream()) {
         //Read stream in full 
    }