Skip to content

Commit

Permalink
Merge pull request #10 from xxDark/master
Browse files Browse the repository at this point in the history
Add fallback path for locked-up environments
  • Loading branch information
Col-E authored May 21, 2022
2 parents 9362c78 + 71d7344 commit ddff64e
Show file tree
Hide file tree
Showing 5 changed files with 212 additions and 23 deletions.
39 changes: 38 additions & 1 deletion src/main/java/software/coley/llzip/util/BufferData.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,39 @@
import java.nio.ByteOrder;

/**
* Mapped file that is backed by byte buffer.
* File that is backed by a byte buffer.
*
* @author xDark
*/
public final class BufferData implements ByteData {
private final ByteBuffer buffer;
private volatile boolean cleaned;

private BufferData(ByteBuffer buffer) {
this.buffer = buffer;
}

@Override
public int getInt(long position) {
ensureOpen();
return buffer.getInt(validate(position));
}

@Override
public short getShort(long position) {
ensureOpen();
return buffer.getShort(validate(position));
}

@Override
public byte get(long position) {
ensureOpen();
return buffer.get(validate(position));
}

@Override
public void get(long position, byte[] b, int off, int len) {
ensureOpen();
ByteBuffer buffer = this.buffer;
((ByteBuffer) buffer.slice()
.order(buffer.order())
Expand All @@ -43,6 +48,7 @@ public void get(long position, byte[] b, int off, int len) {

@Override
public void transferTo(OutputStream out, byte[] buf) throws IOException {
ensureOpen();
ByteBuffer buffer = this.buffer;
int remaining = buffer.remaining();
if (buffer.hasArray()) {
Expand All @@ -62,11 +68,13 @@ public void transferTo(OutputStream out, byte[] buf) throws IOException {

@Override
public ByteData slice(long startIndex, long endIndex) {
ensureOpen();
return new BufferData(ByteDataUtil.sliceExact(buffer, validate(startIndex), validate(endIndex)));
}

@Override
public long length() {
ensureOpen();
return ByteDataUtil.length(buffer);
}

Expand All @@ -83,6 +91,35 @@ public int hashCode() {
return buffer.hashCode();
}

@Override
public void close() {
if (!cleaned) {
synchronized (this) {
if (cleaned)
return;
cleaned = true;
ByteBuffer buffer = this.buffer;
if (buffer.isDirect()) {
CleanerUtil.invokeCleaner(buffer);
}
}
}
}

@Override
protected void finalize() throws Throwable {
try {
close();
} finally {
super.finalize();
}
}

private void ensureOpen() {
if (cleaned)
throw new IllegalStateException("Cannot access data after close");
}

private static int validate(long v) {
if (v < 0L || v > Integer.MAX_VALUE) {
throw new IllegalArgumentException(Long.toString(v));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/software/coley/llzip/util/ByteData.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package software.coley.llzip.util;

import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;

Expand All @@ -8,7 +9,7 @@
*
* @author xDark
*/
public interface ByteData {
public interface ByteData extends Closeable {
/**
* Gets int at specific position.
*
Expand Down
91 changes: 91 additions & 0 deletions src/main/java/software/coley/llzip/util/CleanerUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package software.coley.llzip.util;

import sun.misc.Unsafe;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;

/**
* Utility to invoke cleaners in a {@link ByteBuffer}.
*
* @author xDark
*/
public final class CleanerUtil {

private static final Method INVOKE_CLEANER;
private static final Method GET_CLEANER;
private static final boolean SUPPORTED;

private CleanerUtil() {
}

/**
* Attempts to clean direct buffer.
*
* @param buffer
* Buffer to clean.
*
* @throws IllegalStateException
* If buffer is not direct, slice or duplicate, or
* cleaner failed to invoke.
*/
public static void invokeCleaner(ByteBuffer buffer) {
if (!buffer.isDirect()) {
throw new IllegalStateException("buffer is not direct");
}
if (!SUPPORTED) {
return;
}
Method getCleaner = GET_CLEANER;
Method invokeCleaner = INVOKE_CLEANER;
try {
if (getCleaner != null) {
Object cleaner = getCleaner.invoke(buffer);
if (cleaner == null) {
throw new IllegalStateException("slice or duplicate");
}
invokeCleaner.invoke(cleaner);
} else {
invokeCleaner.invoke(UnsafeUtil.get(), buffer);
}
} catch(InvocationTargetException ex) {
throw new IllegalStateException("Failed to invoke clean method", ex.getTargetException());
} catch(IllegalAccessException ex) {
throw new IllegalStateException("cleaner became inaccessible", ex);
}
}

static {
boolean supported = false;
Method invokeCleaner;
Method getCleaner = null;
try {
invokeCleaner = Unsafe.class.getDeclaredMethod("invokeCleaner", ByteBuffer.class);
invokeCleaner.setAccessible(true);
ByteBuffer tmp = ByteBuffer.allocateDirect(1);
invokeCleaner.invoke(UnsafeUtil.get(), tmp);
supported = true;
} catch(NoSuchMethodException ignored) {
supported = true;
ByteBuffer tmp = ByteBuffer.allocateDirect(1);
try {
Class<?> directBuffer = Class.forName("sun.nio.ch.DirectBuffer");
getCleaner = directBuffer.getDeclaredMethod("cleaner");
invokeCleaner = getCleaner.getReturnType().getDeclaredMethod("clean");
invokeCleaner.setAccessible(true);
getCleaner.setAccessible(true);
invokeCleaner.invoke(getCleaner.invoke(tmp));
} catch(Exception ignored1) {
invokeCleaner = null;
getCleaner = null;
supported = false;
}
} catch(Exception ex) {
invokeCleaner = null;
}
INVOKE_CLEANER = invokeCleaner;
GET_CLEANER = getCleaner;
SUPPORTED = supported;
}
}
67 changes: 50 additions & 17 deletions src/main/java/software/coley/llzip/util/FileMapUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteOrder;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

Expand All @@ -27,9 +30,24 @@ public class FileMapUtil {
*
* @throws IOException
* If any I/O error occurs.
* @throws IllegalStateException
* If the environment is locked up and
* file is larger than 2GB.
*/
public static ByteData map(Path path) throws IOException {
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
if (MAP == null) {
long size = Files.size(path);
if (size <= Integer.MAX_VALUE) {
try(FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
long length = fc.size();
MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0L, length);
buffer.order(ByteOrder.LITTLE_ENDIAN);
return BufferData.wrap(buffer);
}
}
throw new IllegalStateException("Cannot map more than 2GB of data in locked up environment");
}
try(FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
long length = fc.size();
long address;
try {
Expand All @@ -38,13 +56,13 @@ public static ByteData map(Path path) throws IOException {
} else {
address = (long) MAP.invoke(fc, 0, 0L, length, false);
}
} catch (InvocationTargetException | IllegalAccessException ex) {
} catch(InvocationTargetException | IllegalAccessException ex) {
throw new IllegalStateException("Could not invoke map0", ex);
}
ByteData mappedFile = new UnsafeMappedFile(address, length, () -> {
try {
UNMAP.invoke(null, address, length);
} catch (IllegalAccessException | InvocationTargetException ex) {
} catch(IllegalAccessException | InvocationTargetException ex) {
throw new InternalError(ex);
}
});
Expand All @@ -54,23 +72,38 @@ public static ByteData map(Path path) throws IOException {

static {
boolean oldMap = false;
try {
Class<?> c = Class.forName("sun.nio.ch.FileChannelImpl");
Method map;
Method map = null;
Method unmap = null;
get:
{
Class<?> c;
try {
c = Class.forName("sun.nio.ch.FileChannelImpl");
} catch(ClassNotFoundException ignored) {
break get;
}
try {
map = c.getDeclaredMethod("map0", int.class, long.class, long.class, boolean.class);
} catch (NoSuchMethodException ex) {
map = c.getDeclaredMethod("map0", int.class, long.class, long.class);
oldMap = true;
} catch(NoSuchMethodException ex) {
try {
map = c.getDeclaredMethod("map0", int.class, long.class, long.class);
oldMap = true;
} catch(NoSuchMethodException ignored) {
break get;
}
}
try {
map.setAccessible(true);
unmap = c.getDeclaredMethod("unmap0", long.class, long.class);
unmap.setAccessible(true);
} catch(Exception ex) {
// Locked up environment, probably threw InaccessibleObjectException
map = null;
unmap = null;
}
map.setAccessible(true);
Method unmap = c.getDeclaredMethod("unmap0", long.class, long.class);
unmap.setAccessible(true);
MAP = map;
UNMAP = unmap;
OLD_MAP = oldMap;
} catch (ClassNotFoundException | NoSuchMethodException ex) {
throw new ExceptionInInitializerError(ex);
}
MAP = map;
UNMAP = unmap;
OLD_MAP = oldMap;
}
}
Loading

0 comments on commit ddff64e

Please sign in to comment.