diff --git a/src/main/java/org/xerial/snappy/SnappyFramed.java b/src/main/java/org/xerial/snappy/SnappyFramed.java index 0fef08b..97f0147 100644 --- a/src/main/java/org/xerial/snappy/SnappyFramed.java +++ b/src/main/java/org/xerial/snappy/SnappyFramed.java @@ -3,10 +3,22 @@ */ package org.xerial.snappy; +import static java.lang.invoke.MethodHandles.constant; +import static java.lang.invoke.MethodHandles.dropArguments; +import static java.lang.invoke.MethodHandles.filterReturnValue; +import static java.lang.invoke.MethodHandles.guardWithTest; +import static java.lang.invoke.MethodHandles.lookup; +import static java.lang.invoke.MethodType.methodType; + import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles.Lookup; +import java.lang.reflect.Field; import java.lang.reflect.Method; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; +import java.security.AccessController; +import java.security.PrivilegedExceptionAction; import java.util.logging.Level; import java.util.logging.Logger; @@ -30,27 +42,76 @@ final class SnappyFramed * Sun specific mechanisms to clean up resources associated with direct byte buffers. */ @SuppressWarnings("unchecked") - private static final Class SUN_DIRECT_BUFFER = (Class) lookupClassQuietly("sun.nio.ch.DirectBuffer"); - private static final Method SUN_BUFFER_CLEANER; - private static final Method SUN_CLEANER_CLEAN; + static final Class DIRECT_BUFFER_CLAZZ = (Class) lookupClassQuietly("java.nio.DirectByteBuffer"); + static final MethodHandle CLEAN_HANDLE; + static { - Method bufferCleaner = null; - Method cleanerClean = null; + // this approach is based off that used by apache lucene and documented here: https://issues.apache.org/jira/browse/LUCENE-6989 + // and https://github.com/apache/lucene-solr/blob/7e03427fa14a024ce257babcb8362d2451941e21/lucene/core/src/java/org/apache/lucene/store/MMapDirectory.java + MethodHandle cleanHandle = null; try { - //operate under the assumption that if the sun direct buffer class exists, - //all of the sun classes exist - if (SUN_DIRECT_BUFFER != null) { - bufferCleaner = SUN_DIRECT_BUFFER.getMethod("cleaner", (Class[]) null); - Class cleanClazz = lookupClassQuietly("sun.misc.Cleaner"); - cleanerClean = cleanClazz.getMethod("clean", (Class[]) null); - } - } - catch (Throwable t) { + final PrivilegedExceptionAction action = new PrivilegedExceptionAction() { + + @Override + public MethodHandle run() throws Exception { + MethodHandle handle = null; + if (DIRECT_BUFFER_CLAZZ != null) { + final Lookup lookup = lookup(); + + try { + // sun.misc.Unsafe unmapping (Java 9+) + final Class unsafeClass = Class.forName("sun.misc.Unsafe"); + // first check if Unsafe has the right method, otherwise we can give up + // without doing any security critical stuff: + final MethodHandle unmapper = lookup.findVirtual(unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class)); + // fetch the unsafe instance and bind it to the virtual MH: + final Field f = unsafeClass.getDeclaredField("theUnsafe"); + f.setAccessible(true); + final Object theUnsafe = f.get(null); + handle = unmapper.bindTo(theUnsafe); + } catch (Exception e) { + Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "unable to use java 9 Unsafe.invokeCleaner", e); + + // sun.misc.Cleaner unmapping (Java 8 and older) + final Method m = DIRECT_BUFFER_CLAZZ.getMethod("cleaner"); + m.setAccessible(true); + final MethodHandle directBufferCleanerMethod = lookup.unreflect(m); + final Class cleanerClass = directBufferCleanerMethod.type().returnType(); + + /* + * "Compile" a MethodHandle that basically is equivalent to the following code: + * void unmapper(ByteBuffer byteBuffer) + * { + * sun.misc.Cleaner cleaner = ((java.nio.DirectByteBuffer) byteBuffer).cleaner(); + * if (nonNull(cleaner)) + * { + * cleaner.clean(); + * } + * else + * { + * // the noop is needed because MethodHandles#guardWithTest always needs ELSE + * noop(cleaner); + * } + * } + */ + final MethodHandle cleanMethod = lookup.findVirtual(cleanerClass, "clean", methodType(void.class)); + final MethodHandle nonNullTest = lookup.findStatic(SnappyFramed.class, "nonNull", methodType(boolean.class, Object.class)).asType(methodType(boolean.class, cleanerClass)); + final MethodHandle noop = dropArguments(constant(Void.class, null).asType(methodType(void.class)), 0, cleanerClass); + handle = filterReturnValue(directBufferCleanerMethod, guardWithTest(nonNullTest, cleanMethod, noop)).asType(methodType(void.class, ByteBuffer.class)); + } + } + + return handle; + } + }; + + cleanHandle = AccessController.doPrivileged(action); + + } catch (Throwable t) { Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "Exception occurred attempting to lookup Sun specific DirectByteBuffer cleaner classes.", t); } - SUN_BUFFER_CLEANER = bufferCleaner; - SUN_CLEANER_CLEAN = cleanerClean; + CLEAN_HANDLE = cleanHandle; } /** @@ -170,18 +231,34 @@ final class SnappyFramed * * @param buffer The {@code ByteBuffer} to release. Must not be {@code null}. Must be {@link ByteBuffer#isDirect() direct}. */ - static void releaseDirectByteBuffer(ByteBuffer buffer) + static void releaseDirectByteBuffer(final ByteBuffer buffer) { assert buffer != null && buffer.isDirect(); - if (SUN_DIRECT_BUFFER != null && SUN_DIRECT_BUFFER.isAssignableFrom(buffer.getClass())) { + if (CLEAN_HANDLE != null && DIRECT_BUFFER_CLAZZ.isInstance(buffer)) { try { - Object cleaner = SUN_BUFFER_CLEANER.invoke(buffer, (Object[]) null); - SUN_CLEANER_CLEAN.invoke(cleaner, (Object[]) null); - } - catch (Throwable t) { + final PrivilegedExceptionAction pea = new PrivilegedExceptionAction() { + @Override + public Void run() throws Exception { + try { + CLEAN_HANDLE.invokeExact(buffer); + } catch (Exception e) { + throw e; + } catch (Throwable t) { + //this will be an error + throw new RuntimeException(t); + } + return null; + } + }; + AccessController.doPrivileged(pea); + } catch (Throwable t) { Logger.getLogger(SnappyFramed.class.getName()).log(Level.FINE, "Exception occurred attempting to clean up Sun specific DirectByteBuffer.", t); } } } + + static boolean nonNull(Object o) { + return o != null; + } }