com.sun.nio.file.ExtendedOpenOption not available in OSGi bundles on Windows since JRuby 9.3.0.0
Environment Information
Provide at least:
- jruby 9.3.6.0
- Windows
Expected Behavior
https://github.com/jruby/jruby/commit/b5b635180e16c6812f009e3c04fb647caa9e0ee0 introduced using of com.sun.nio.file.ExtendedOpenOption just for adding NOSHARE_DELETE when opening files on Windows.
} else if (Platform.IS_WINDOWS) {
options[index++] = ExtendedOpenOption.NOSHARE_DELETE;
}
Previously on JRuby 9.2 this option was not added.
Actual Behavior
If JRuby is loaded inside an OSGi bundle then it is not possible to access internal com.sun.* classes (it is not allowed to add them in Import-Package in the bundle manifest file). As a result opening of files on Windows in OSGi bundle fails with
NoClassDefFoundError: com/sun/nio/file/ExtendedOpenOption
Currently, we have patched JRuby and removed adding this ExtendedOpenOption.NOSHARE_DELETE on Windows.
It would be good to catch NoClassDefFoundError and ignore it silently to enable using unpatched JRuby in OSGi bundles on Windows.
Looks like there are suggestions for how to work around this on older JDKs in the main answer here:
https://stackoverflow.com/questions/39293193/random-access-file-filelock-java-io-vs-java-nio
The extended options are mention, but the better alternative appears to be to use NIO.2 APIs to open the file rather than the old FileInput/OutputStream constructors.
It's possible the additional option usage may be unnecessary. If I'm reading that SO answer and the related links correctly, moving to NIO APIs for opening files should already be using the correct flags. We may have assumed it would not use the correct flags and added the additional code to make sure.
After reading of that SO answer, I was wondering if moving to NIO APIs will allow deleting of files that are open from a different JRuby process? Currently, this is different on Linux and Windows - on Linux, open files by a JRuby process can be deleted, but on Windows, they cannot be deleted.
This prevents restarting of stuck Jira Server on Windows with our plugin (as an OSGi module) installed.
@rsim Yeah it is a little confusing, since there's this flag NOSHARE_DELETE and then a newer option SHARE_DELETE that seems to do the same thing? I will try to sort it out, and hopefully we can just leverage NIO whenever that option is available.
Ok, so I sorted some of this out.
SHARE_DELETE is a Windows flag that allows opening files in such a way that they can be deleted by other processes. This is unused by default in the classic java.io APIs and enabled by default when opening files via NIO. NOSHARE_DELETE is the JVM flag you can pass to NIO to turn SHARE_DELETE off again.
By default JRuby is attempting to disable SHARE_DELETE, unless you pass in the Ruby flag File::SHARE_DELETE. If that flag is not given, then we attempt to use NOSHARE_DELETE.
And then of course NOSHARE_DELETE is not part of the public JDK modules, so that leads to problems when it is inaccessible.
The simplest fix at this point would be to attempt to access NOSHARE_DELETE in static code and disable this logic when it is inaccessible. We will then just use NIO default SHARE_DELETE behavior, since there's no other way to turn it off (or at least, no other way I know of yet).
OK, it would be good to fix that NOSHARE_DELETE is used only if the class com.sun.nio.file.ExtendedOpenOption is accessible by the current classloader.
I pushed #7304 but can't really test it without an example. @rsim Can you give this a try, and if possible contribute a test for this case?
After reading of that SO answer, I was wondering if moving to NIO APIs will allow deleting of files that are open from a different JRuby process?
@rsim By default, JRuby (and I think CRuby too) defaults to no SHARE_DELETE flag, which is why we use that ExtendedOption NOSHARE_DELETE. Ruby code can request SHARE_DELETE via something like File.open("blah", "w", File::SHARE_DELETE) but I think this should actually be the default as on JDK/NIO.
So any case where we cannot access NOSHARE_DELETE will now default to SHARE_DELETE, which is a compatibility difference I'm ok with. I would be willing to help lobby CRuby to always open files with SHARE_DELETE, if they don't already, since it would make behavior more consistent across platforms.
Optimistically closing this. Hopefully the fix in #7304 fixes the issue for you.