Skip to content

Commit ddff64e

Browse files
authored
Merge pull request #10 from xxDark/master
Add fallback path for locked-up environments
2 parents 9362c78 + 71d7344 commit ddff64e

File tree

5 files changed

+212
-23
lines changed

5 files changed

+212
-23
lines changed

src/main/java/software/coley/llzip/util/BufferData.java

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,34 +6,39 @@
66
import java.nio.ByteOrder;
77

88
/**
9-
* Mapped file that is backed by byte buffer.
9+
* File that is backed by a byte buffer.
1010
*
1111
* @author xDark
1212
*/
1313
public final class BufferData implements ByteData {
1414
private final ByteBuffer buffer;
15+
private volatile boolean cleaned;
1516

1617
private BufferData(ByteBuffer buffer) {
1718
this.buffer = buffer;
1819
}
1920

2021
@Override
2122
public int getInt(long position) {
23+
ensureOpen();
2224
return buffer.getInt(validate(position));
2325
}
2426

2527
@Override
2628
public short getShort(long position) {
29+
ensureOpen();
2730
return buffer.getShort(validate(position));
2831
}
2932

3033
@Override
3134
public byte get(long position) {
35+
ensureOpen();
3236
return buffer.get(validate(position));
3337
}
3438

3539
@Override
3640
public void get(long position, byte[] b, int off, int len) {
41+
ensureOpen();
3742
ByteBuffer buffer = this.buffer;
3843
((ByteBuffer) buffer.slice()
3944
.order(buffer.order())
@@ -43,6 +48,7 @@ public void get(long position, byte[] b, int off, int len) {
4348

4449
@Override
4550
public void transferTo(OutputStream out, byte[] buf) throws IOException {
51+
ensureOpen();
4652
ByteBuffer buffer = this.buffer;
4753
int remaining = buffer.remaining();
4854
if (buffer.hasArray()) {
@@ -62,11 +68,13 @@ public void transferTo(OutputStream out, byte[] buf) throws IOException {
6268

6369
@Override
6470
public ByteData slice(long startIndex, long endIndex) {
71+
ensureOpen();
6572
return new BufferData(ByteDataUtil.sliceExact(buffer, validate(startIndex), validate(endIndex)));
6673
}
6774

6875
@Override
6976
public long length() {
77+
ensureOpen();
7078
return ByteDataUtil.length(buffer);
7179
}
7280

@@ -83,6 +91,35 @@ public int hashCode() {
8391
return buffer.hashCode();
8492
}
8593

94+
@Override
95+
public void close() {
96+
if (!cleaned) {
97+
synchronized (this) {
98+
if (cleaned)
99+
return;
100+
cleaned = true;
101+
ByteBuffer buffer = this.buffer;
102+
if (buffer.isDirect()) {
103+
CleanerUtil.invokeCleaner(buffer);
104+
}
105+
}
106+
}
107+
}
108+
109+
@Override
110+
protected void finalize() throws Throwable {
111+
try {
112+
close();
113+
} finally {
114+
super.finalize();
115+
}
116+
}
117+
118+
private void ensureOpen() {
119+
if (cleaned)
120+
throw new IllegalStateException("Cannot access data after close");
121+
}
122+
86123
private static int validate(long v) {
87124
if (v < 0L || v > Integer.MAX_VALUE) {
88125
throw new IllegalArgumentException(Long.toString(v));

src/main/java/software/coley/llzip/util/ByteData.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package software.coley.llzip.util;
22

3+
import java.io.Closeable;
34
import java.io.IOException;
45
import java.io.OutputStream;
56

@@ -8,7 +9,7 @@
89
*
910
* @author xDark
1011
*/
11-
public interface ByteData {
12+
public interface ByteData extends Closeable {
1213
/**
1314
* Gets int at specific position.
1415
*
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package software.coley.llzip.util;
2+
3+
import sun.misc.Unsafe;
4+
5+
import java.lang.reflect.InvocationTargetException;
6+
import java.lang.reflect.Method;
7+
import java.nio.ByteBuffer;
8+
9+
/**
10+
* Utility to invoke cleaners in a {@link ByteBuffer}.
11+
*
12+
* @author xDark
13+
*/
14+
public final class CleanerUtil {
15+
16+
private static final Method INVOKE_CLEANER;
17+
private static final Method GET_CLEANER;
18+
private static final boolean SUPPORTED;
19+
20+
private CleanerUtil() {
21+
}
22+
23+
/**
24+
* Attempts to clean direct buffer.
25+
*
26+
* @param buffer
27+
* Buffer to clean.
28+
*
29+
* @throws IllegalStateException
30+
* If buffer is not direct, slice or duplicate, or
31+
* cleaner failed to invoke.
32+
*/
33+
public static void invokeCleaner(ByteBuffer buffer) {
34+
if (!buffer.isDirect()) {
35+
throw new IllegalStateException("buffer is not direct");
36+
}
37+
if (!SUPPORTED) {
38+
return;
39+
}
40+
Method getCleaner = GET_CLEANER;
41+
Method invokeCleaner = INVOKE_CLEANER;
42+
try {
43+
if (getCleaner != null) {
44+
Object cleaner = getCleaner.invoke(buffer);
45+
if (cleaner == null) {
46+
throw new IllegalStateException("slice or duplicate");
47+
}
48+
invokeCleaner.invoke(cleaner);
49+
} else {
50+
invokeCleaner.invoke(UnsafeUtil.get(), buffer);
51+
}
52+
} catch(InvocationTargetException ex) {
53+
throw new IllegalStateException("Failed to invoke clean method", ex.getTargetException());
54+
} catch(IllegalAccessException ex) {
55+
throw new IllegalStateException("cleaner became inaccessible", ex);
56+
}
57+
}
58+
59+
static {
60+
boolean supported = false;
61+
Method invokeCleaner;
62+
Method getCleaner = null;
63+
try {
64+
invokeCleaner = Unsafe.class.getDeclaredMethod("invokeCleaner", ByteBuffer.class);
65+
invokeCleaner.setAccessible(true);
66+
ByteBuffer tmp = ByteBuffer.allocateDirect(1);
67+
invokeCleaner.invoke(UnsafeUtil.get(), tmp);
68+
supported = true;
69+
} catch(NoSuchMethodException ignored) {
70+
supported = true;
71+
ByteBuffer tmp = ByteBuffer.allocateDirect(1);
72+
try {
73+
Class<?> directBuffer = Class.forName("sun.nio.ch.DirectBuffer");
74+
getCleaner = directBuffer.getDeclaredMethod("cleaner");
75+
invokeCleaner = getCleaner.getReturnType().getDeclaredMethod("clean");
76+
invokeCleaner.setAccessible(true);
77+
getCleaner.setAccessible(true);
78+
invokeCleaner.invoke(getCleaner.invoke(tmp));
79+
} catch(Exception ignored1) {
80+
invokeCleaner = null;
81+
getCleaner = null;
82+
supported = false;
83+
}
84+
} catch(Exception ex) {
85+
invokeCleaner = null;
86+
}
87+
INVOKE_CLEANER = invokeCleaner;
88+
GET_CLEANER = getCleaner;
89+
SUPPORTED = supported;
90+
}
91+
}

src/main/java/software/coley/llzip/util/FileMapUtil.java

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import java.io.IOException;
44
import java.lang.reflect.InvocationTargetException;
55
import java.lang.reflect.Method;
6+
import java.nio.ByteOrder;
7+
import java.nio.MappedByteBuffer;
68
import java.nio.channels.FileChannel;
9+
import java.nio.file.Files;
710
import java.nio.file.Path;
811
import java.nio.file.StandardOpenOption;
912

@@ -27,9 +30,24 @@ public class FileMapUtil {
2730
*
2831
* @throws IOException
2932
* If any I/O error occurs.
33+
* @throws IllegalStateException
34+
* If the environment is locked up and
35+
* file is larger than 2GB.
3036
*/
3137
public static ByteData map(Path path) throws IOException {
32-
try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
38+
if (MAP == null) {
39+
long size = Files.size(path);
40+
if (size <= Integer.MAX_VALUE) {
41+
try(FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
42+
long length = fc.size();
43+
MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0L, length);
44+
buffer.order(ByteOrder.LITTLE_ENDIAN);
45+
return BufferData.wrap(buffer);
46+
}
47+
}
48+
throw new IllegalStateException("Cannot map more than 2GB of data in locked up environment");
49+
}
50+
try(FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) {
3351
long length = fc.size();
3452
long address;
3553
try {
@@ -38,13 +56,13 @@ public static ByteData map(Path path) throws IOException {
3856
} else {
3957
address = (long) MAP.invoke(fc, 0, 0L, length, false);
4058
}
41-
} catch (InvocationTargetException | IllegalAccessException ex) {
59+
} catch(InvocationTargetException | IllegalAccessException ex) {
4260
throw new IllegalStateException("Could not invoke map0", ex);
4361
}
4462
ByteData mappedFile = new UnsafeMappedFile(address, length, () -> {
4563
try {
4664
UNMAP.invoke(null, address, length);
47-
} catch (IllegalAccessException | InvocationTargetException ex) {
65+
} catch(IllegalAccessException | InvocationTargetException ex) {
4866
throw new InternalError(ex);
4967
}
5068
});
@@ -54,23 +72,38 @@ public static ByteData map(Path path) throws IOException {
5472

5573
static {
5674
boolean oldMap = false;
57-
try {
58-
Class<?> c = Class.forName("sun.nio.ch.FileChannelImpl");
59-
Method map;
75+
Method map = null;
76+
Method unmap = null;
77+
get:
78+
{
79+
Class<?> c;
80+
try {
81+
c = Class.forName("sun.nio.ch.FileChannelImpl");
82+
} catch(ClassNotFoundException ignored) {
83+
break get;
84+
}
6085
try {
6186
map = c.getDeclaredMethod("map0", int.class, long.class, long.class, boolean.class);
62-
} catch (NoSuchMethodException ex) {
63-
map = c.getDeclaredMethod("map0", int.class, long.class, long.class);
64-
oldMap = true;
87+
} catch(NoSuchMethodException ex) {
88+
try {
89+
map = c.getDeclaredMethod("map0", int.class, long.class, long.class);
90+
oldMap = true;
91+
} catch(NoSuchMethodException ignored) {
92+
break get;
93+
}
94+
}
95+
try {
96+
map.setAccessible(true);
97+
unmap = c.getDeclaredMethod("unmap0", long.class, long.class);
98+
unmap.setAccessible(true);
99+
} catch(Exception ex) {
100+
// Locked up environment, probably threw InaccessibleObjectException
101+
map = null;
102+
unmap = null;
65103
}
66-
map.setAccessible(true);
67-
Method unmap = c.getDeclaredMethod("unmap0", long.class, long.class);
68-
unmap.setAccessible(true);
69-
MAP = map;
70-
UNMAP = unmap;
71-
OLD_MAP = oldMap;
72-
} catch (ClassNotFoundException | NoSuchMethodException ex) {
73-
throw new ExceptionInInitializerError(ex);
74104
}
105+
MAP = map;
106+
UNMAP = unmap;
107+
OLD_MAP = oldMap;
75108
}
76109
}

0 commit comments

Comments
 (0)