Skip to content

Commit 117f161

Browse files
authored
Support JDK17 (#660)
* Reproduce #600 (JDK17 error) * Add workaround for Java17 * Add a helpful error message * Add note on JDK17
1 parent d665284 commit 117f161

File tree

5 files changed

+96
-21
lines changed

5 files changed

+96
-21
lines changed

.github/workflows/CI.yml

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,33 @@ jobs:
2626
- uses: actions/checkout@v2
2727
- name: jcheckstyle
2828
run: ./sbt jcheckStyle
29+
test_jdk17:
30+
name: Test JDK17
31+
runs-on: ubuntu-latest
32+
steps:
33+
- uses: actions/checkout@v2
34+
- uses: actions/setup-java@v3
35+
with:
36+
distribution: 'zulu'
37+
java-version: '17'
38+
- uses: actions/cache@v2
39+
with:
40+
path: ~/.cache
41+
key: ${{ runner.os }}-jdk11-${{ hashFiles('**/*.sbt') }}
42+
restore-keys: ${{ runner.os }}-jdk17-
43+
- name: Test
44+
run: ./sbt test
45+
- name: Universal Buffer Test
46+
run: ./sbt test -J-Dmsgpack.universal-buffer=true
2947
test_jdk11:
3048
name: Test JDK11
3149
runs-on: ubuntu-latest
3250
steps:
3351
- uses: actions/checkout@v2
34-
- uses: olafurpg/setup-scala@v10
52+
- uses: actions/setup-java@v3
3553
with:
36-
java-version: adopt@1.11
54+
distribution: 'zulu'
55+
java-version: '11'
3756
- uses: actions/cache@v2
3857
with:
3958
path: ~/.cache
@@ -48,9 +67,10 @@ jobs:
4867
runs-on: ubuntu-latest
4968
steps:
5069
- uses: actions/checkout@v2
51-
- uses: olafurpg/setup-scala@v10
70+
- uses: actions/setup-java@v3
5271
with:
53-
java-version: adopt@1.8
72+
distribution: 'zulu'
73+
java-version: '8'
5474
- uses: actions/cache@v2
5575
with:
5676
path: ~/.cache

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,15 @@ dependencies {
4242

4343
- [Usage examples](https://github.com/msgpack/msgpack-java/blob/develop/msgpack-core/src/test/java/org/msgpack/core/example/MessagePackExample.java)
4444

45+
### Java 17 Support
46+
47+
For using DirectByteBuffer (off-heap memory access methods) in JDK17, you need to specify two JVM options:
48+
```
49+
--add-opens=java.base/java.nio=ALL-UNNAMED
50+
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED
51+
```
52+
53+
4554
### Integration with Jackson ObjectMapper (jackson-databind)
4655

4756
msgpack-java supports serialization and deserialization of Java objects through [jackson-databind](https://github.com/FasterXML/jackson-databind).

build.sbt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ lazy val msgpackCore = Project(id = "msgpack-core", base = file("msgpack-core"))
7474
"org.msgpack.value.impl"
7575
),
7676
testFrameworks += new TestFramework("wvlet.airspec.Framework"),
77+
Test / javaOptions ++= Seq(
78+
// --add-opens is not available in JDK8
79+
"-XX:+IgnoreUnrecognizedVMOptions",
80+
"--add-opens=java.base/java.nio=ALL-UNNAMED",
81+
"--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
82+
),
83+
Test / fork := true,
7784
libraryDependencies ++= Seq(
7885
// msgpack-core should have no external dependencies
7986
junitInterface,

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

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,19 +18,22 @@
1818
import java.lang.reflect.Constructor;
1919
import java.lang.reflect.InvocationTargetException;
2020
import java.lang.reflect.Method;
21+
import java.nio.Buffer;
2122
import java.nio.ByteBuffer;
2223
import java.security.AccessController;
2324
import java.security.PrivilegedAction;
2425

2526
import sun.misc.Unsafe;
27+
import sun.nio.ch.DirectBuffer;
2628

2729
/**
2830
* Wraps the difference of access methods to DirectBuffers between Android and others.
2931
*/
3032
class DirectBufferAccess
3133
{
3234
private DirectBufferAccess()
33-
{}
35+
{
36+
}
3437

3538
enum DirectBufferConstructorType
3639
{
@@ -40,7 +43,6 @@ enum DirectBufferConstructorType
4043
ARGS_MB_INT_INT
4144
}
4245

43-
static Method mGetAddress;
4446
// For Java <=8, gets a sun.misc.Cleaner
4547
static Method mCleaner;
4648
static Method mClean;
@@ -95,10 +97,19 @@ enum DirectBufferConstructorType
9597
if (byteBufferConstructor == null) {
9698
throw new RuntimeException("Constructor of DirectByteBuffer is not found");
9799
}
98-
byteBufferConstructor.setAccessible(true);
99100

100-
mGetAddress = directByteBufferClass.getDeclaredMethod("address");
101-
mGetAddress.setAccessible(true);
101+
try {
102+
byteBufferConstructor.setAccessible(true);
103+
}
104+
catch (RuntimeException e) {
105+
// This is a Java9+ exception, so we need to detect it without importing it for Java8 support
106+
if ("java.lang.reflect.InaccessibleObjectException".equals(e.getClass().getName())) {
107+
byteBufferConstructor = null;
108+
}
109+
else {
110+
throw e;
111+
}
112+
}
102113

103114
if (MessageBuffer.javaVersion <= 8) {
104115
setupCleanerJava6(direct);
@@ -160,6 +171,7 @@ public Object run()
160171

161172
/**
162173
* Checks if we have a usable {@link DirectByteBuffer#cleaner}.
174+
*
163175
* @param direct a direct buffer
164176
* @return the method or an error
165177
*/
@@ -184,6 +196,7 @@ private static Object getCleanerMethod(ByteBuffer direct)
184196

185197
/**
186198
* Checks if we have a usable {@link sun.misc.Cleaner#clean}.
199+
*
187200
* @param direct a direct buffer
188201
* @param mCleaner the {@link DirectByteBuffer#cleaner} method
189202
* @return the method or null
@@ -210,6 +223,7 @@ private static Object getCleanMethod(ByteBuffer direct, Method mCleaner)
210223

211224
/**
212225
* Checks if we have a usable {@link Unsafe#invokeCleaner}.
226+
*
213227
* @param direct a direct buffer
214228
* @return the method or an error
215229
*/
@@ -218,7 +232,7 @@ private static Object getInvokeCleanerMethod(ByteBuffer direct)
218232
try {
219233
// See https://bugs.openjdk.java.net/browse/JDK-8171377
220234
Method m = MessageBuffer.unsafe.getClass().getDeclaredMethod(
221-
"invokeCleaner", ByteBuffer.class);
235+
"invokeCleaner", ByteBuffer.class);
222236
m.invoke(MessageBuffer.unsafe, direct);
223237
return m;
224238
}
@@ -233,17 +247,9 @@ private static Object getInvokeCleanerMethod(ByteBuffer direct)
233247
}
234248
}
235249

236-
static long getAddress(Object base)
250+
static long getAddress(Buffer buffer)
237251
{
238-
try {
239-
return (Long) mGetAddress.invoke(base);
240-
}
241-
catch (IllegalAccessException e) {
242-
throw new RuntimeException(e);
243-
}
244-
catch (InvocationTargetException e) {
245-
throw new RuntimeException(e);
246-
}
252+
return ((DirectBuffer) buffer).address();
247253
}
248254

249255
static void clean(Object base)
@@ -253,7 +259,7 @@ static void clean(Object base)
253259
Object cleaner = mCleaner.invoke(base);
254260
mClean.invoke(cleaner);
255261
}
256-
else {
262+
else {
257263
mInvokeCleaner.invoke(MessageBuffer.unsafe, base);
258264
}
259265
}
@@ -269,6 +275,10 @@ static boolean isDirectByteBufferInstance(Object s)
269275

270276
static ByteBuffer newByteBuffer(long address, int index, int length, ByteBuffer reference)
271277
{
278+
if (byteBufferConstructor == null) {
279+
throw new IllegalStateException("Can't create a new DirectByteBuffer. In JDK17+, two JVM options needs to be set: " +
280+
"--add-opens=java.base/java.nio=ALL-UNNAMED and --add-opens=java.base/sun.nio.ch=ALL-UNNAMED");
281+
}
272282
try {
273283
switch (directBufferConstructorType) {
274284
case ARGS_LONG_INT_REF:
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// MessagePack for Java
3+
//
4+
// Licensed under the Apache License, Version 2.0 (the "License");
5+
// you may not use this file except in compliance with the License.
6+
// You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
//
16+
package org.msgpack.core.buffer
17+
18+
import wvlet.airspec.AirSpec
19+
20+
import java.nio.ByteBuffer
21+
22+
class DirectBufferAccessTest extends AirSpec {
23+
24+
test("instantiate DirectBufferAccess") {
25+
val bb = ByteBuffer.allocateDirect(1)
26+
val addr = DirectBufferAccess.getAddress(bb)
27+
28+
}
29+
}

0 commit comments

Comments
 (0)