blob: 4b7253f7e8086b826c9a662caf4d2dede1ddf7bb [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.maven.buildcache.hash;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.security.PrivilegedAction;
import static java.security.AccessController.doPrivileged;
import static org.apache.maven.buildcache.hash.ReflectionUtils.getField;
import static org.apache.maven.buildcache.hash.ReflectionUtils.getMethod;
/**
* CloseableBuffer https://stackoverflow.com/a/54046774
*/
public class CloseableBuffer implements AutoCloseable
{
private static final Cleaner CLEANER = doPrivileged( new PrivilegedAction<Cleaner>()
{
@Override
public Cleaner run()
{
final String jsv = System.getProperty( "java.specification.version", "9" );
if ( jsv.startsWith( "1." ) )
{
return DirectCleaner.isSupported() ? new DirectCleaner() : new NoopCleaner();
}
else
{
return UnsafeCleaner.isSupported() ? new UnsafeCleaner() : new NoopCleaner();
}
}
} );
public static CloseableBuffer directBuffer( int capacity )
{
return new CloseableBuffer( ByteBuffer.allocateDirect( capacity ) );
}
public static CloseableBuffer mappedBuffer( FileChannel channel, MapMode mode ) throws IOException
{
return new CloseableBuffer( channel.map( mode, 0, channel.size() ) );
}
private ByteBuffer buffer;
/**
* Unmap only DirectByteBuffer and MappedByteBuffer
*/
private CloseableBuffer( ByteBuffer buffer )
{
// Java 8: buffer.isDirect()
this.buffer = buffer;
}
/**
* Do not use buffer after close
*/
public ByteBuffer getBuffer()
{
return buffer;
}
@Override
public void close()
{
// Java 8: () -> CLEANER.clean(buffer)
boolean done = doPrivileged( new PrivilegedAction<Boolean>()
{
@Override
public Boolean run()
{
return CLEANER.clean( buffer );
}
} );
if ( done )
{
buffer = null;
}
}
// Java 8: @FunctionalInterface
private interface Cleaner
{
boolean clean( ByteBuffer buffer );
}
private static class NoopCleaner implements Cleaner
{
@Override
public boolean clean( ByteBuffer buffer )
{
return false;
}
}
private static class DirectCleaner implements Cleaner
{
private static final Method ATTACHMENT = getMethod( "sun.nio.ch.DirectBuffer",
"attachment" );
private static final Method CLEANER = getMethod( "sun.nio.ch.DirectBuffer", "cleaner" );
private static final Method CLEAN = getMethod( "sun.misc.Cleaner", "clean" );
public static boolean isSupported()
{
return ATTACHMENT != null && CLEAN != null && CLEANER != null;
}
/**
* Make sure duplicates and slices are not cleaned, since this can result in duplicate attempts to clean the
* same buffer, which trigger a crash with: "A fatal error has been detected by the Java Runtime Environment:
* EXCEPTION_ACCESS_VIOLATION" See: https://stackoverflow.com/a/31592947/3950982
*/
@Override
public boolean clean( ByteBuffer buffer )
{
try
{
if ( ATTACHMENT.invoke( buffer ) == null )
{
CLEAN.invoke( CLEANER.invoke( buffer ) );
return true;
}
}
catch ( Exception ignore )
{
}
return false;
}
}
private static class UnsafeCleaner implements Cleaner
{
// Java 9: getMethod("jdk.internal.misc.Unsafe", "invokeCleaner", ByteBuffer.class);
private static final Method INVOKE_CLEANER = getMethod( "sun.misc.Unsafe", "invokeCleaner", ByteBuffer.class );
private static final Object UNSAFE = getField( "sun.misc.Unsafe", "theUnsafe" );
public static boolean isSupported()
{
return UNSAFE != null && INVOKE_CLEANER != null;
}
/**
* Calling the above code in JDK9+ gives a reflection warning on stderr,
* Unsafe.theUnsafe.invokeCleaner(byteBuffer) makes the same call, but does not print the reflection warning
*/
@Override
public boolean clean( ByteBuffer buffer )
{
try
{
// throws IllegalArgumentException if buffer is a duplicate or slice
INVOKE_CLEANER.invoke( UNSAFE, buffer );
return true;
}
catch ( Exception ignore )
{
}
return false;
}
}
}