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