[ 
https://issues.apache.org/jira/browse/IO-895?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Jeroen Peschier updated IO-895:
-------------------------------
    Description: 
When migrating a project to Java 25 with modularization, I ran into an issue 
where Commons IO 2.22.0 throws an Error when invoking 
BufferedFileChannelInputStream::close with Commons IO JAR on the module path 
while having "requires org.apache.commons.io;" in the module-info:
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanDirectBuffer(BufferedFileChannelInputStream.java:230)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.clean(BufferedFileChannelInputStream.java:217)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:240)
    ... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a Cleaner.
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:102)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:79)
    ... 6 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
    at 
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at java.base/java.lang.Class.forName(Class.java:458)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:47)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:41)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:97)
    ... 7 more{noformat}
Code that reproduces the above stacktrace:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
       .setPath("some-existing-file")
       .get();
input.close();{code}
I noticed that the current HEAD 
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
  has an extra setter "setClean" for the builder that aims to skip the cleanup:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
       .setPath("some-existing-file")
       .setClean(false)
       .get();
input.close();{code}
However running with a snapshot of 
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
 and using "setClean(false)" I am seeing the same exception: 
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanBuffer(BufferedFileChannelInputStream.java:252)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:261)
    ... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a direct 
ByteBuffer Cleaner.
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:116)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:82)
    ... 3 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
    at 
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at java.base/java.lang.Class.forName(Class.java:458)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:50)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:44)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:111)
    ... 4 more{noformat}
So both 2.22.0 and 2.23.0-SNAPSHOT fails on construction of 
ByteBufferCleaner.Java8Cleaner when executed from the module path. Note that 
the stacktrace is misleading: it appears to fail on the Java8Cleaner (it does), 
but it actually also attempts to create a Java9Cleaner and when that fails it 
prints the stacktrace ofthe Java8Cleaner failure hiding that the creation of 
the Java9Cleaner also failed.

The Common IO unit tests seem to run on the classpath so they don't highlight 
this issue, although there are warnings when running with the classpath as well:
{noformat}
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called 
WARNING: sun.misc.Unsafe::invokeCleaner has been called by 
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner 
(file:/REDACTED/.m2/repository/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar)
 WARNING: Please consider reporting this to the maintainers of class 
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner WARNING: 
sun.misc.Unsafe::invokeCleaner will be removed in a future release{noformat}
An obvious workaround is to put the Commons IO jar on the classpath. Beside 
negating the benefits of Java modules, this is not an (easy) option for 
application distributions that no longer have a classpath.

Ideas on how to fix this:
 - Maybe ByteBufferCleaner needs a Java8 and Java9+ version in a multi-release 
JAR? That way the ExceptionInInitializerError could be prevented altogether.
 - Since Java 9, the replacement for sun.misc.Cleaner is 
jdk.internal.ref.Cleaner. Still internal to the JDK though.

  was:
When migrating a project to Java 25 with modularization, I ran into an issue 
where Commons IO 2.22.0 throws an Error when invoking 
BufferedFileChannelInputStream::close with Commons IO JAR on the module path 
while having "requires org.apache.commons.io;" in the module-info:
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanDirectBuffer(BufferedFileChannelInputStream.java:230)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.clean(BufferedFileChannelInputStream.java:217)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:240)
    ... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a Cleaner.
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:102)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:79)
    ... 6 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
    at 
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at java.base/java.lang.Class.forName(Class.java:458)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:47)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:41)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:97)
    ... 7 more{noformat}
Code that reproduces the above stacktrace:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
       .setPath("some-existing-file")
       .get();
input.close();{code}
I noticed that the current HEAD 
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
  has an extra setter "setClean" for the builder that aims to skip the cleanup:
{code:java}
var input = new BufferedFileChannelInputStream.Builder()
       .setPath("some-existing-file")
       .setClean(false)
       .get();
input.close();{code}
However running with a snapshot of 
([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
 and using "setClean(false)" I am seeing the same exception: 
{noformat}
Exception in thread "main" java.lang.ExceptionInInitializerError
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanBuffer(BufferedFileChannelInputStream.java:252)
    at 
[email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:261)
    ... snip private code
Caused by: java.lang.IllegalStateException: Failed to initialize a direct 
ByteBuffer Cleaner.
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:116)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:82)
    ... 3 more
Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
    at 
java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
    at java.base/java.lang.Class.forName0(Native Method)
    at java.base/java.lang.Class.forName(Class.java:467)
    at java.base/java.lang.Class.forName(Class.java:458)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:50)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:44)
    at 
[email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:111)
    ... 4 more{noformat}
So both 2.22.0 and 2.23.0-SNAPSHOT fails on construction of 
ByteBufferCleaner.Java8Cleaner  when executed from the module path. It never 
gets to a Java9+ compatible cleaner because ExceptionInInitializerError (not an 
Exception) never triggers the fall-through exception handling in 
ByteBufferCleaner::getCleaner.

The Common IO unit tests seem to run on the classpath so they don't highlight 
this issue, although there are warnings when running with the classpath as well:
{noformat}
WARNING: A terminally deprecated method in sun.misc.Unsafe has been called 
WARNING: sun.misc.Unsafe::invokeCleaner has been called by 
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner 
(file:/REDACTED/.m2/repository/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar)
 WARNING: Please consider reporting this to the maintainers of class 
org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner WARNING: 
sun.misc.Unsafe::invokeCleaner will be removed in a future release{noformat}
An obvious workaround is to put the Commons IO jar on the classpath. Beside 
negating the benefits of Java modules, this is not an (easy) option for 
application distributions that no longer have a classpath.

Ideas on how to fix this:
 - Maybe ByteBufferCleaner needs a Java8 and Java9+ version in a multi-release 
JAR? That way the ExceptionInInitializerError could be prevented altogether.
 - Since Java 9, the replacement for sun.misc.Cleaner is 
jdk.internal.ref.Cleaner. Still internal to the JDK though.


> ExceptionInInitializerError when using Commons IO on the module path
> --------------------------------------------------------------------
>
>                 Key: IO-895
>                 URL: https://issues.apache.org/jira/browse/IO-895
>             Project: Commons IO
>          Issue Type: Bug
>    Affects Versions: 2.22.0
>         Environment: Java 25, Linux
>            Reporter: Jeroen Peschier
>            Priority: Blocker
>
> When migrating a project to Java 25 with modularization, I ran into an issue 
> where Commons IO 2.22.0 throws an Error when invoking 
> BufferedFileChannelInputStream::close with Commons IO JAR on the module path 
> while having "requires org.apache.commons.io;" in the module-info:
> {noformat}
> Exception in thread "main" java.lang.ExceptionInInitializerError
>     at 
> [email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanDirectBuffer(BufferedFileChannelInputStream.java:230)
>     at 
> [email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.clean(BufferedFileChannelInputStream.java:217)
>     at 
> [email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:240)
>     ... snip private code
> Caused by: java.lang.IllegalStateException: Failed to initialize a Cleaner.
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:102)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:79)
>     ... 6 more
> Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
>     at 
> java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
>     at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
>     at java.base/java.lang.Class.forName0(Native Method)
>     at java.base/java.lang.Class.forName(Class.java:467)
>     at java.base/java.lang.Class.forName(Class.java:458)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:47)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:41)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:97)
>     ... 7 more{noformat}
> Code that reproduces the above stacktrace:
> {code:java}
> var input = new BufferedFileChannelInputStream.Builder()
>        .setPath("some-existing-file")
>        .get();
> input.close();{code}
> I noticed that the current HEAD 
> ([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
>   has an extra setter "setClean" for the builder that aims to skip the 
> cleanup:
> {code:java}
> var input = new BufferedFileChannelInputStream.Builder()
>        .setPath("some-existing-file")
>        .setClean(false)
>        .get();
> input.close();{code}
> However running with a snapshot of 
> ([f9de7a4|https://github.com/apache/commons-io/commit/f9de7a458b9ff70151994bbbacae3088047b8338])
>  and using "setClean(false)" I am seeing the same exception: 
> {noformat}
> Exception in thread "main" java.lang.ExceptionInInitializerError
>     at 
> [email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.cleanBuffer(BufferedFileChannelInputStream.java:252)
>     at 
> [email protected]/org.apache.commons.io.input.BufferedFileChannelInputStream.close(BufferedFileChannelInputStream.java:261)
>     ... snip private code
> Caused by: java.lang.IllegalStateException: Failed to initialize a direct 
> ByteBuffer Cleaner.
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:116)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.<clinit>(ByteBufferCleaner.java:82)
>     ... 3 more
> Caused by: java.lang.ClassNotFoundException: sun.misc.Cleaner
>     at 
> java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:580)
>     at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:490)
>     at java.base/java.lang.Class.forName0(Native Method)
>     at java.base/java.lang.Class.forName(Class.java:467)
>     at java.base/java.lang.Class.forName(Class.java:458)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:50)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner$Java8Cleaner.<init>(ByteBufferCleaner.java:44)
>     at 
> [email protected]/org.apache.commons.io.input.ByteBufferCleaner.getCleaner(ByteBufferCleaner.java:111)
>     ... 4 more{noformat}
> So both 2.22.0 and 2.23.0-SNAPSHOT fails on construction of 
> ByteBufferCleaner.Java8Cleaner when executed from the module path. Note that 
> the stacktrace is misleading: it appears to fail on the Java8Cleaner (it 
> does), but it actually also attempts to create a Java9Cleaner and when that 
> fails it prints the stacktrace ofthe Java8Cleaner failure hiding that the 
> creation of the Java9Cleaner also failed.
> The Common IO unit tests seem to run on the classpath so they don't highlight 
> this issue, although there are warnings when running with the classpath as 
> well:
> {noformat}
> WARNING: A terminally deprecated method in sun.misc.Unsafe has been called 
> WARNING: sun.misc.Unsafe::invokeCleaner has been called by 
> org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner 
> (file:/REDACTED/.m2/repository/commons-io/commons-io/2.22.0/commons-io-2.22.0.jar)
>  WARNING: Please consider reporting this to the maintainers of class 
> org.apache.commons.io.input.ByteBufferCleaner$Java9Cleaner WARNING: 
> sun.misc.Unsafe::invokeCleaner will be removed in a future release{noformat}
> An obvious workaround is to put the Commons IO jar on the classpath. Beside 
> negating the benefits of Java modules, this is not an (easy) option for 
> application distributions that no longer have a classpath.
> Ideas on how to fix this:
>  - Maybe ByteBufferCleaner needs a Java8 and Java9+ version in a 
> multi-release JAR? That way the ExceptionInInitializerError could be 
> prevented altogether.
>  - Since Java 9, the replacement for sun.misc.Cleaner is 
> jdk.internal.ref.Cleaner. Still internal to the JDK though.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to