Fix NPE when closing a stream from a different thread (#167)
* Fix NPE when closing a stream from a different thread
Than the one that opened the stream
* Add tests for closing streams in a different thread
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/FileContentThreadData.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/FileContentThreadData.java
index 4e60f20..daf9283 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/FileContentThreadData.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/FileContentThreadData.java
@@ -79,7 +79,19 @@
}
void remove(final InputStream inputStream) {
- this.inputStreamList.remove(inputStream);
+ // this null-check (as well as the one in the other `remove` method) should not
+ // be needed because `remove` is called only in `DefaultFileContent.endInput` which
+ // should only be called after an input stream has been created and hence the `inputStreamList`
+ // variable initialized. However, `DefaultFileContent` uses this class per thread -
+ // so it is possible to get a stream, pass it to another thread and close it there -
+ // and that would lead to a NPE here if it weren't for that check. This "solution" here -
+ // adding a null-check - is really "bad" in the sense that it will fix a crash but there will
+ // be a leak because the input stream won't be removed from the original thread's `inputStreamList`.
+ // See https://github.com/apache/commons-vfs/pull/166 for more context.
+ // TODO: fix this problem
+ if (this.inputStreamList != null) {
+ this.inputStreamList.remove(inputStream);
+ }
}
Object removeRandomAccessContent(final int pos) {
@@ -87,7 +99,9 @@
}
void remove(final RandomAccessContent randomAccessContent) {
- this.randomAccessContentList.remove(randomAccessContent);
+ if (this.randomAccessContentList != null) {
+ this.randomAccessContentList.remove(randomAccessContent);
+ }
}
void setOutputStream(final DefaultFileContent.FileContentOutputStream outputStream) {
diff --git a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/DefaultFileContentTest.java b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/DefaultFileContentTest.java
index 405913f..14d646a 100644
--- a/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/DefaultFileContentTest.java
+++ b/commons-vfs2/src/test/java/org/apache/commons/vfs2/provider/DefaultFileContentTest.java
@@ -16,6 +16,7 @@
*/
package org.apache.commons.vfs2.provider;
+import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -24,6 +25,7 @@
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.function.FailableFunction;
import org.apache.commons.vfs2.FileContent;
import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemManager;
@@ -80,6 +82,11 @@
}
@Test
+ public void testInputStreamClosedInADifferentThread() throws Exception {
+ testStreamClosedInADifferentThread(content -> content.getInputStream());
+ }
+
+ @Test
public void testMarkingWhenReadingEOS() throws Exception {
final File temp = File.createTempFile("temp-file-name", ".tmp");
final FileSystemManager fileSystemManager = VFS.getManager();
@@ -167,4 +174,28 @@
}
}
+ @Test
+ public void testOutputStreamClosedInADifferentThread() throws Exception {
+ testStreamClosedInADifferentThread(content -> content.getOutputStream());
+ }
+
+ private <T extends Closeable> void testStreamClosedInADifferentThread(FailableFunction<FileContent, T, IOException> getStream) throws Exception {
+ final File temp = File.createTempFile("temp-file-name", ".tmp");
+ final FileSystemManager fileSystemManager = VFS.getManager();
+
+ try (FileObject file = fileSystemManager.resolveFile(temp.getAbsolutePath())) {
+ T stream = getStream.apply(file.getContent());
+ boolean[] check = { false };
+ Thread thread = new Thread(() -> {
+ try {
+ stream.close();
+ } catch (IOException exception) {
+ }
+ check[0] = true;
+ });
+ thread.start();
+ thread.join();
+ Assert.assertTrue(check[0]);
+ }
+ }
}