FULL PRODUCT VERSION :
java version "1.7.0_51"
OpenJDK Runtime Environment (IcedTea 2.4.4) (7u51-2.4.4-0ubuntu0.13.10.1)
OpenJDK 64-Bit Server VM (build 24.45-b08, mixed mode)
ADDITIONAL OS VERSION INFORMATION :
Linux mhassert 3.11.0-17-generic #31-Ubuntu SMP Mon Feb 3 21:52:43 UTC 2014 x86_64 x86_64 x86_64 GNU/Linux
EXTRA RELEVANT SYSTEM CONFIGURATION :
zipfs.jar containing package "com/sun/nio/zipfs" must be in classpath.
Using Ubuntu, this is provided by the package "openjdk-7-jre-headless"
A DESCRIPTION OF THE PROBLEM :
Summary:
When com.sun.nio.zipfs.ZipFileSystemProvider is present, all calls to java.nio.file.FileSystems.newFileSystem() for non-zip files leak an open file descriptor.
Details:
- When one calls java.nio.file.FileSystems.newFileSystem() for an existing file, all installed FileSystemProviders are checked by calling .newFileSystem() on them.
- com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem() checks whether ZipFileSystem is applicable by trying to create a new Instance of it.
- The constructor ZipFileSystem() opens in its second last line a new Channel to the file:
this.ch = Files.newByteChannel(zfpath, READ);
- In its last line it tries to access the zip content, aborting with an exception if not successfull:
this.cen = initCEN();
- In case of an exception the channel "this.ch" is never closed.
Solution:
I propose wrapping the last line in a try-catch-rethrow block, closing the channel in case of an exception.
Note on bug priority:
I am aware that ZipFileSystem is merely a demo and not an integral part of jre. But ironically this bug does not affect users of ZipFileSystem. It is a show-stopper for everyone using java.nio.file.FileSystems.newFileSystem() for any other kind of custom file system. It is enough to have ZipFileSystem on your system.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Steps to reproduce:
- Call java.nio.file.FileSystems.newFileSystem() for an existing file that is not a valid zip file. (An empty file will do)
- ProviderNotFoundException is thrown as expected.
- A file descriptor to the file is kept alive. (using Linux, check with lsof | grep PID)
- By repeating this steps you will eventually hit the file descriptor limit of your OS and a FileSystemException is thrown.
See also provided test case.
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I expect to safely call java.nio.file.FileSystems.newFileSystem() for non-zip files without side effects.
All file descriptors opened by ZipFileSystem and ZipFileSystemProvider should be released when the file is not a zip file.
ACTUAL -
- A file descriptor to the file is kept alive. (using Linux, check with lsof | grep PID)
ERROR MESSAGES/STACK TRACES THAT OCCUR :
Exception in thread "main" java.nio.file.FileSystemException: /tmp/test: Too many open files
at sun.nio.fs.UnixException.translateToIOException(UnixException.java:91)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:102)
at sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:107)
at sun.nio.fs.UnixFileSystemProvider.newByteChannel(UnixFileSystemProvider.java:214)
at java.nio.file.Files.newByteChannel(Files.java:315)
at java.nio.file.Files.newByteChannel(Files.java:361)
at com.sun.nio.zipfs.ZipFileSystem.<init>(ZipFileSystem.java:129)
at com.sun.nio.zipfs.ZipFileSystemProvider.newFileSystem(ZipFileSystemProvider.java:139)
at java.nio.file.FileSystems.newFileSystem(FileSystems.java:386)
at de.abm.dependencies.ZipFileSystemBug.main(ZipFileSystemBug.java:30)
REPRODUCIBILITY :
This bug can be reproduced always.
---------- BEGIN SOURCE ----------
// file ZipFileSystemBug.java
import java.nio.file.FileSystem;
import java.nio.file.FileSystemException;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.ProviderNotFoundException;
/**
* @author mhassert
*
*/
public class ZipFileSystemBug {
private static final String EXISTING_NON_ZIP_FILE = "/tmp/test";
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
Path path = Paths.get(EXISTING_NON_ZIP_FILE);
int i = 0;
while (true) {
try (FileSystem fs = FileSystems.newFileSystem(path, null);) {
throw new Exception("THIS SHOULD NEVER HAPPEN");
} catch (final ProviderNotFoundException e) {
// EXPECTED
System.out.println(i++);
} catch (FileSystemException e) {
// NOT EXPECTED!
e.printStackTrace();
throw e;
}
}
}
}
---------- END SOURCE ----------