Skip to content

Commit bef0ccd

Browse files
ppkarwaszxerial
authored andcommitted
Improves MessageBuffer support for Java 11 (#514)
* Improves MessageBuffer support of Java 11 Switches the MessageBuffer implementation to MessageBufferU for Java versions at least 9. Since this implementation overrides almost all MessageBuffer's method it is safe to allow direct buffers as argument to MessageBuffer.wrap(ByteBuffer) * Corrects code style * Do not switch to MessageBufferU on Java 9+ Disables the automatic switch to MessageBufferU on Java 9+, falling back to a manual switch through Java properties. * Run Java 11 tests on universal buffer only Java 11 tests without the "msgpack.universal-buffer" property set where using the universal buffer anyway: Java 11's "java.specification.version" does not contain a dot, so MessageBuffer misidentified it as Java less than 7 and switched to MessageBufferU. * Fixes DirectBufferAccess#clean on Java 11 For Java 9+ we switch from a DirectByteBuffer.cleaner().clean() call to Unsafe.invokeCleaner(buffer). * Corrects style * Corrects whitespace * Restores Java8 tests. * Fixes IllegalAccessExceptions Adds missing setAccessible calls.
1 parent ba61279 commit bef0ccd

File tree

3 files changed

+191
-28
lines changed

3 files changed

+191
-28
lines changed

msgpack-core/src/main/java/org/msgpack/core/buffer/DirectBufferAccess.java

Lines changed: 145 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
import java.lang.reflect.InvocationTargetException;
2020
import java.lang.reflect.Method;
2121
import java.nio.ByteBuffer;
22+
import java.security.AccessController;
23+
import java.security.PrivilegedAction;
24+
25+
import sun.misc.Unsafe;
2226

2327
/**
2428
* Wraps the difference of access methods to DirectBuffers between Android and others.
@@ -37,20 +41,24 @@ enum DirectBufferConstructorType
3741
}
3842

3943
static Method mGetAddress;
44+
// For Java <=8, gets a sun.misc.Cleaner
4045
static Method mCleaner;
4146
static Method mClean;
47+
// For Java >=9, invokes a jdk.internal.ref.Cleaner
48+
static Method mInvokeCleaner;
4249

4350
// TODO We should use MethodHandle for efficiency, but it is not available in JDK6
44-
static Constructor byteBufferConstructor;
51+
static Constructor<?> byteBufferConstructor;
4552
static Class<?> directByteBufferClass;
4653
static DirectBufferConstructorType directBufferConstructorType;
4754
static Method memoryBlockWrapFromJni;
4855

4956
static {
5057
try {
58+
final ByteBuffer direct = ByteBuffer.allocateDirect(1);
5159
// Find the hidden constructor for DirectByteBuffer
52-
directByteBufferClass = ClassLoader.getSystemClassLoader().loadClass("java.nio.DirectByteBuffer");
53-
Constructor directByteBufferConstructor = null;
60+
directByteBufferClass = direct.getClass();
61+
Constructor<?> directByteBufferConstructor = null;
5462
DirectBufferConstructorType constructorType = null;
5563
Method mbWrap = null;
5664
try {
@@ -92,17 +100,139 @@ enum DirectBufferConstructorType
92100
mGetAddress = directByteBufferClass.getDeclaredMethod("address");
93101
mGetAddress.setAccessible(true);
94102

95-
mCleaner = directByteBufferClass.getDeclaredMethod("cleaner");
96-
mCleaner.setAccessible(true);
97-
98-
mClean = mCleaner.getReturnType().getDeclaredMethod("clean");
99-
mClean.setAccessible(true);
103+
if (MessageBuffer.javaVersion <= 8) {
104+
setupCleanerJava6(direct);
105+
}
106+
else {
107+
setupCleanerJava9(direct);
108+
}
100109
}
101110
catch (Exception e) {
102111
throw new RuntimeException(e);
103112
}
104113
}
105114

115+
private static void setupCleanerJava6(final ByteBuffer direct)
116+
{
117+
Object obj;
118+
obj = AccessController.doPrivileged(new PrivilegedAction<Object>()
119+
{
120+
@Override
121+
public Object run()
122+
{
123+
return getCleanerMethod(direct);
124+
}
125+
});
126+
if (obj instanceof Throwable) {
127+
throw new RuntimeException((Throwable) obj);
128+
}
129+
mCleaner = (Method) obj;
130+
131+
obj = AccessController.doPrivileged(new PrivilegedAction<Object>()
132+
{
133+
@Override
134+
public Object run()
135+
{
136+
return getCleanMethod(direct, mCleaner);
137+
}
138+
});
139+
if (obj instanceof Throwable) {
140+
throw new RuntimeException((Throwable) obj);
141+
}
142+
mClean = (Method) obj;
143+
}
144+
145+
private static void setupCleanerJava9(final ByteBuffer direct)
146+
{
147+
Object obj = AccessController.doPrivileged(new PrivilegedAction<Object>()
148+
{
149+
@Override
150+
public Object run()
151+
{
152+
return getInvokeCleanerMethod(direct);
153+
}
154+
});
155+
if (obj instanceof Throwable) {
156+
throw new RuntimeException((Throwable) obj);
157+
}
158+
mInvokeCleaner = (Method) obj;
159+
}
160+
161+
/**
162+
* Checks if we have a usable {@link DirectByteBuffer#cleaner}.
163+
* @param direct a direct buffer
164+
* @return the method or an error
165+
*/
166+
private static Object getCleanerMethod(ByteBuffer direct)
167+
{
168+
try {
169+
Method m = direct.getClass().getDeclaredMethod("cleaner");
170+
m.setAccessible(true);
171+
m.invoke(direct);
172+
return m;
173+
}
174+
catch (NoSuchMethodException e) {
175+
return e;
176+
}
177+
catch (InvocationTargetException e) {
178+
return e;
179+
}
180+
catch (IllegalAccessException e) {
181+
return e;
182+
}
183+
}
184+
185+
/**
186+
* Checks if we have a usable {@link sun.misc.Cleaner#clean}.
187+
* @param direct a direct buffer
188+
* @param mCleaner the {@link DirectByteBuffer#cleaner} method
189+
* @return the method or null
190+
*/
191+
private static Object getCleanMethod(ByteBuffer direct, Method mCleaner)
192+
{
193+
try {
194+
Method m = mCleaner.getReturnType().getDeclaredMethod("clean");
195+
Object c = mCleaner.invoke(direct);
196+
m.setAccessible(true);
197+
m.invoke(c);
198+
return m;
199+
}
200+
catch (NoSuchMethodException e) {
201+
return e;
202+
}
203+
catch (InvocationTargetException e) {
204+
return e;
205+
}
206+
catch (IllegalAccessException e) {
207+
return e;
208+
}
209+
}
210+
211+
/**
212+
* Checks if we have a usable {@link Unsafe#invokeCleaner}.
213+
* @param direct a direct buffer
214+
* @return the method or an error
215+
*/
216+
private static Object getInvokeCleanerMethod(ByteBuffer direct)
217+
{
218+
try {
219+
// See https://bugs.openjdk.java.net/browse/JDK-8171377
220+
Method m = MessageBuffer.unsafe.getClass().getDeclaredMethod(
221+
"invokeCleaner", ByteBuffer.class);
222+
m.invoke(MessageBuffer.unsafe, direct);
223+
return m;
224+
}
225+
catch (NoSuchMethodException e) {
226+
return e;
227+
}
228+
catch (InvocationTargetException e) {
229+
return e;
230+
}
231+
catch (IllegalAccessException e) {
232+
return e;
233+
}
234+
}
235+
106236
static long getAddress(Object base)
107237
{
108238
try {
@@ -119,8 +249,13 @@ static long getAddress(Object base)
119249
static void clean(Object base)
120250
{
121251
try {
122-
Object cleaner = mCleaner.invoke(base);
123-
mClean.invoke(cleaner);
252+
if (MessageBuffer.javaVersion <= 8) {
253+
Object cleaner = mCleaner.invoke(base);
254+
mClean.invoke(cleaner);
255+
}
256+
else {
257+
mInvokeCleaner.invoke(MessageBuffer.unsafe, base);
258+
}
124259
}
125260
catch (Throwable e) {
126261
throw new RuntimeException(e);

msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBuffer.java

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public class MessageBuffer
4747
{
4848
static final boolean isUniversalBuffer;
4949
static final Unsafe unsafe;
50+
static final int javaVersion = getJavaVersion();
5051

5152
/**
5253
* Reference to MessageBuffer Constructors
@@ -69,21 +70,6 @@ public class MessageBuffer
6970
int arrayByteBaseOffset = 16;
7071

7172
try {
72-
// Check java version
73-
String javaVersion = System.getProperty("java.specification.version", "");
74-
int dotPos = javaVersion.indexOf('.');
75-
boolean isJavaAtLeast7 = false;
76-
if (dotPos != -1) {
77-
try {
78-
int major = Integer.parseInt(javaVersion.substring(0, dotPos));
79-
int minor = Integer.parseInt(javaVersion.substring(dotPos + 1));
80-
isJavaAtLeast7 = major > 1 || (major == 1 && minor >= 7);
81-
}
82-
catch (NumberFormatException e) {
83-
e.printStackTrace(System.err);
84-
}
85-
}
86-
8773
boolean hasUnsafe = false;
8874
try {
8975
hasUnsafe = Class.forName("sun.misc.Unsafe") != null;
@@ -97,12 +83,12 @@ public class MessageBuffer
9783
// Is Google App Engine?
9884
boolean isGAE = System.getProperty("com.google.appengine.runtime.version") != null;
9985

100-
// For Java6, android and JVM that has no Unsafe class, use Universal MessageBuffer
86+
// For Java6, android and JVM that has no Unsafe class, use Universal MessageBuffer (based on ByteBuffer).
10187
useUniversalBuffer =
10288
Boolean.parseBoolean(System.getProperty("msgpack.universal-buffer", "false"))
10389
|| isAndroid
10490
|| isGAE
105-
|| !isJavaAtLeast7
91+
|| javaVersion < 7
10692
|| !hasUnsafe;
10793

10894
if (!useUniversalBuffer) {
@@ -175,6 +161,31 @@ public class MessageBuffer
175161
}
176162
}
177163

164+
private static int getJavaVersion()
165+
{
166+
String javaVersion = System.getProperty("java.specification.version", "");
167+
int dotPos = javaVersion.indexOf('.');
168+
if (dotPos != -1) {
169+
try {
170+
int major = Integer.parseInt(javaVersion.substring(0, dotPos));
171+
int minor = Integer.parseInt(javaVersion.substring(dotPos + 1));
172+
return major > 1 ? major : minor;
173+
}
174+
catch (NumberFormatException e) {
175+
e.printStackTrace(System.err);
176+
}
177+
}
178+
else {
179+
try {
180+
return Integer.parseInt(javaVersion);
181+
}
182+
catch (NumberFormatException e) {
183+
e.printStackTrace(System.err);
184+
}
185+
}
186+
return 6;
187+
}
188+
178189
/**
179190
* Base object for resolving the relative address of the raw byte array.
180191
* If base == null, the address value is a raw memory address
@@ -366,7 +377,12 @@ else if (DirectBufferAccess.isDirectByteBufferInstance(buffer.reference)) {
366377
{
367378
if (bb.isDirect()) {
368379
if (isUniversalBuffer) {
369-
throw new UnsupportedOperationException("Cannot create MessageBuffer from a DirectBuffer on this platform");
380+
// MessageBufferU overrides almost all methods, only field 'size' is used.
381+
this.base = null;
382+
this.address = 0;
383+
this.size = bb.remaining();
384+
this.reference = null;
385+
return;
370386
}
371387
// Direct buffer or off-heap memory
372388
this.base = null;

msgpack-core/src/main/java/org/msgpack/core/buffer/MessageBufferU.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,16 @@ public byte[] toByteArray()
258258
getBytes(0, b, 0, b.length);
259259
return b;
260260
}
261+
262+
@Override
263+
public boolean hasArray()
264+
{
265+
return !wrap.isDirect();
266+
}
267+
268+
@Override
269+
public byte[] array()
270+
{
271+
return hasArray() ? wrap.array() : null;
272+
}
261273
}

0 commit comments

Comments
 (0)