Seeking inputs on 8224794: ZipFile/JarFile should open zip/JAR file with FILE_SHARE_DELETE sharing mode on Windows

Jaikiran Pai jai.forums2013 at gmail.com
Fri Nov 19 11:40:25 UTC 2021


I have been experimenting with 
https://bugs.openjdk.java.net/browse/JDK-8224794 and have reached a 
stage with my changes where I would like some inputs before I move ahead 
or raise a PR for review.

As noted in that JBS issue, the idea is to open the underlying file 
using FILE_SHARE_DELETE when using java.uitl.zip.ZipFile and 
java.util.jar.JarFile. From what I can infer from that JBS issue, the 
motivation behind this seems to be to allow deleting that underlying 
file when that file is currently opened and in use by the 
ZipFile/JarFile instance. The linked github issues seem to suggest the 
same. IMO, it's a reasonable expectation, given that such a deletion 
works on *nix platform. It's on Windows where such a deletion fails with 
"The process cannot access the file because it is being used by another 
process" (the other process in many cases will be the current JVM 
instance itself). So trying to get this to work on Windows by setting 
the FILE_SHARE_DELETE share access mode seems reasonable.

Coming to the implementation part, I've an initial draft version here 
https://github.com/openjdk/jdk/pull/6477. It sets the FILE_SHARE_DELETE 
share access mode on the file when constructed by ZipFile/JarFile. It 
"works", in the sense that if you issue a delete against this underlying 
file (path) after its been opened by ZipFile/JarFile instance, you no 
longer see the error mentioned above and the delete completes without 
any exception. However, my experiments with this change have raised a 
bunch of questions related to this:

The crucial part of the "DeleteFile" API in Windows is that, as stated 
in its documentation[1]:

"The DeleteFile function marks a file for deletion on close. Therefore, 
the file deletion does not occur until the last handle to the file is 
closed. Subsequent calls to CreateFile to open the file fail with 
ERROR_ACCESS_DENIED."

So imagine the following pseudo-code:

String path = "C:/foo.jar"
1. JarFile jar = new JarFile(path); // create a jar instance
2. var deleted = new File(path).delete(); // delete the underlying file
3. var exists = new File(path).exists(); // check file existence
4. FileOutputStream fos = new FileOutputStream(new File(path)); // open 
a FileOutputStream for /tmp/foo.jar
5. java.nio.file.Files.deleteIfExists(Path.of(path));

When this is run (on Windows) *without* the change that introduces 
FILE_SHARE_DELETE, I notice that line#2 returns "false" for 
File.delete() (since the file is in use by the JarFile instance) and 
then line#3 returns "true" since it still exists and line#4 succeeds and 
creates a new FileOutputStream and line#5 fails with an exception 
stating "java.nio.file.FileSystemException: foo.jar: The process cannot 
access the file because it is being used by another process
"

Now when the same code is run (on Windows) *with* the proposed 
FILE_SHARE_DELETE changes, I notice that line#2 returns "true" (i.e. the 
file is considered deleted) and line#3 returns "true" (i.e. it's 
considered the file still exists). So essentially the file is only 
marked for deletion and hasn't been deleted at the OS level, since the 
JarFile instance still has an handle open. Then line#4 unlike 
previously, now throws an exception and fails to create the 
FileOutputStream instance. The failure says 
"java.nio.file.AccessDeniedException: foo.jar". This exception matches 
the documentation from that Windows DeleteFile API. line#5 too fails 
with this same AccessDeniedException.

So what this means is, although we have managed to allow the deletion to 
happen (i.e. the first call to delete does mark it for deletion at OS 
level), IMO, it still doesn't improve much and raises other difference 
in behaviour of APIs as seen above, unless the underlying file handle 
held by ZipFile/JarFile is closed. So that brings me to the next related 
issue.

For this change to be really useful, the file handle (which is an 
instance of RandomAccessFile) held by ZipFile/JarFile needs to be 
closed. If a user application explicitly creates an instance of 
ZipFile/JarFile, it is expected that they close it themselves. However, 
the most prominent use of the JarFile creation is within the JDK code 
where it uses the sun.net.www.protocol.jar.JarURLConnection to construct 
the JarFile instances, typically from the URLClassLoader. By default 
these JarFile instances that are created by the 
sun.net.www.protocol.jar.JarURLConnection are cached and the close() on 
those instances is called only when the URLClassLoader is close()ed. 
That in itself doesn't guarantee that the underlying file descriptor 
corresponding to the JarFile instance gets closed, because there's one 
more level of reference counting "cache" in use in 
java.util.zip.ZipFile$Source class where the same underlying file 
descriptor (corresponding to the jar file) gets used by multiple JarFile 
instances (irrespective of who created those). So in short, the 
underlying file descriptor of a JarFile only gets closed when every 
single instance of a JarFile pointing to the same jar file has been 
close()ed within the JVM. Which effectively means that introducing the 
FILE_SHARE_DELETE semantics/changes for Windows will only allow the 
first delete() attempt to succeed (since that one marks it for 
deletion), but any subsequent operations like the one noted in the 
examples, that rely on that file being deleted will continue to fail 
with Access Denied errors. Given all this caching of JarFile and the 
reference count cache for the underlying jar file, is this change of 
FILE_SHARE_DELETE adding much value and should I pursue it further?

[1] 
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-deletefilew

-Jaikiran



More information about the core-libs-dev mailing list