[SPARK-36704][CORE] Expand exception handling to more Java 9 cases where reflection is limited at runtime, when reflecting to manage DirectByteBuffer settings

### What changes were proposed in this pull request?

Improve exception handling in the Platform initialization, where it attempts to assess whether reflection is possible to modify DirectByteBuffer. This can apparently fail in more cases on Java 9+ than are currently handled, whereas Spark can continue without reflection if needed.

More detailed comments on the change inline.

### Why are the changes needed?

This exception seems to be possible and fails startup:

```
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make private java.nio.DirectByteBuffer(long,int) accessible: module java.base does not "opens java.nio" to unnamed module 71e9ddb4
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:357)
        at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
        at java.base/java.lang.reflect.Constructor.checkCanSetAccessible(Constructor.java:188)
        at java.base/java.lang.reflect.Constructor.setAccessible(Constructor.java:181)
        at org.apache.spark.unsafe.Platform.<clinit>(Platform.java:56)
```

### Does this PR introduce _any_ user-facing change?

Should strictly allow Spark to continue in more cases.

### How was this patch tested?

Existing tests.

Closes #33947 from srowen/SPARK-36704.

Authored-by: Sean Owen <srowen@gmail.com>
Signed-off-by: Sean Owen <srowen@gmail.com>
This commit is contained in:
Sean Owen 2021-09-11 13:38:10 -05:00
parent 1f679ed8e9
commit e5283f5ed5

View file

@ -45,30 +45,20 @@ public final class Platform {
private static final boolean unaligned; private static final boolean unaligned;
// Access fields and constructors once and store them, for performance:
private static final Constructor<?> DBB_CONSTRUCTOR;
private static final Field DBB_CLEANER_FIELD;
static {
try {
Class<?> cls = Class.forName("java.nio.DirectByteBuffer");
Constructor<?> constructor = cls.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
constructor.setAccessible(true);
Field cleanerField = cls.getDeclaredField("cleaner");
cleanerField.setAccessible(true);
DBB_CONSTRUCTOR = constructor;
DBB_CLEANER_FIELD = cleanerField;
} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
throw new IllegalStateException(e);
}
}
// Split java.version on non-digit chars: // Split java.version on non-digit chars:
private static final int majorVersion = private static final int majorVersion =
Integer.parseInt(System.getProperty("java.version").split("\\D+")[0]); Integer.parseInt(System.getProperty("java.version").split("\\D+")[0]);
// Access fields and constructors once and store them, for performance:
private static final Constructor<?> DBB_CONSTRUCTOR;
private static final Field DBB_CLEANER_FIELD;
private static final Method CLEANER_CREATE_METHOD; private static final Method CLEANER_CREATE_METHOD;
static { static {
// At the end of this block, CLEANER_CREATE_METHOD should be non-null iff it's possible to use
// reflection to invoke it, which is not necessarily possible by default in Java 9+.
// Code below can test for null to see whether to use it.
// The implementation of Cleaner changed from JDK 8 to 9 // The implementation of Cleaner changed from JDK 8 to 9
String cleanerClassName; String cleanerClassName;
if (majorVersion < 9) { if (majorVersion < 9) {
@ -77,6 +67,29 @@ public final class Platform {
cleanerClassName = "jdk.internal.ref.Cleaner"; cleanerClassName = "jdk.internal.ref.Cleaner";
} }
try { try {
Class<?> cls = Class.forName("java.nio.DirectByteBuffer");
Constructor<?> constructor = cls.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
Field cleanerField = cls.getDeclaredField("cleaner");
try {
constructor.setAccessible(true);
cleanerField.setAccessible(true);
} catch (RuntimeException re) {
// This is a Java 9+ exception, so needs to be handled without importing it
if ("InaccessibleObjectException".equals(re.getClass().getSimpleName())) {
// Continue, but the constructor/field are not available
// See comment below for more context
constructor = null;
cleanerField = null;
} else {
throw re;
}
}
// Have to set these values no matter what:
DBB_CONSTRUCTOR = constructor;
DBB_CLEANER_FIELD = cleanerField;
// no point continuing if the above failed:
if (DBB_CONSTRUCTOR != null && DBB_CLEANER_FIELD != null) {
Class<?> cleanerClass = Class.forName(cleanerClassName); Class<?> cleanerClass = Class.forName(cleanerClassName);
Method createMethod = cleanerClass.getMethod("create", Object.class, Runnable.class); Method createMethod = cleanerClass.getMethod("create", Object.class, Runnable.class);
// Accessing jdk.internal.ref.Cleaner should actually fail by default in JDK 9+, // Accessing jdk.internal.ref.Cleaner should actually fail by default in JDK 9+,
@ -90,15 +103,17 @@ public final class Platform {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
// Don't throw an exception, but can't log here? // Don't throw an exception, but can't log here?
createMethod = null; createMethod = null;
} catch (InvocationTargetException ite) {
// shouldn't happen; report it
throw new IllegalStateException(ite);
} }
CLEANER_CREATE_METHOD = createMethod; CLEANER_CREATE_METHOD = createMethod;
} catch (ClassNotFoundException | NoSuchMethodException e) { } else {
throw new IllegalStateException(e); CLEANER_CREATE_METHOD = null;
}
} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
// These are all fatal in any Java version - rethrow (have to wrap as this is a static block)
throw new IllegalStateException(e);
} catch (InvocationTargetException ite) {
throw new IllegalStateException(ite.getCause());
} }
} }
/** /**