Skip to content

Commit

Permalink
Properly target Java 8
Browse files Browse the repository at this point in the history
Java 9+ added an overload of various methods in ByteBuffer. When compiling with Java 11+ and targeting Java 8 bytecode
the resulting signatures are invalid for JDK 8, so accesses below result in NoSuchMethodError. Accessing the
methods through the interface class works around the problem.

Ideally, we'd use "javac --release 8", but that doesn't work because Unsafe is not available in the signature data
for that profile.
  • Loading branch information
martint committed Aug 11, 2021
1 parent 2c1391a commit 181f668
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 183 deletions.
9 changes: 7 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ jobs:
java: ['8', '11', '15', '16']
steps:
- uses: actions/checkout@v1
- name: Setup JDK ${{ matrix.java }}
- name: Setup test JDK ${{ matrix.java }}
uses: actions/setup-java@v1
with:
java-version: ${{ matrix.java }}
- run: echo "test_java_home=$JAVA_HOME" >> $GITHUB_ENV
- name: Setup build JDK 16
uses: actions/setup-java@v1
with:
java-version: 16
- name: Maven Test
run: mvn install -B
run: mvn install -V -P ci -B -Dtest_java_home=${{ env.test_java_home }}
17 changes: 17 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,21 @@
</plugins>
</pluginManagement>
</build>

<profiles>
<profile>
<id>ci</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<jvm>${test_java_home}/bin/java</jvm>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>
50 changes: 29 additions & 21 deletions src/main/java/io/airlift/compress/lz4/Lz4Compressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import io.airlift.compress.Compressor;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import static io.airlift.compress.lz4.Lz4RawCompressor.MAX_TABLE_SIZE;
Expand Down Expand Up @@ -47,48 +48,55 @@ public int compress(byte[] input, int inputOffset, int inputLength, byte[] outpu
@Override
public void compress(ByteBuffer input, ByteBuffer output)
{
// Java 9+ added an overload of various methods in ByteBuffer. When compiling with Java 11+ and targeting Java 8 bytecode
// the resulting signatures are invalid for JDK 8, so accesses below result in NoSuchMethodError. Accessing the
// methods through the interface class works around the problem
// Sidenote: we can't target "javac --release 8" because Unsafe is not available in the signature data for that profile
Buffer inputBuffer = input;
Buffer outputBuffer = output;

Object inputBase;
long inputAddress;
long inputLimit;
if (input.isDirect()) {
if (inputBuffer.isDirect()) {
inputBase = null;
long address = getAddress(input);
inputAddress = address + input.position();
inputLimit = address + input.limit();
long address = getAddress(inputBuffer);
inputAddress = address + inputBuffer.position();
inputLimit = address + inputBuffer.limit();
}
else if (input.hasArray()) {
inputBase = input.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit();
else if (inputBuffer.hasArray()) {
inputBase = inputBuffer.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName());
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + inputBuffer.getClass().getName());
}

Object outputBase;
long outputAddress;
long outputLimit;
if (output.isDirect()) {
if (outputBuffer.isDirect()) {
outputBase = null;
long address = getAddress(output);
outputAddress = address + output.position();
outputLimit = address + output.limit();
long address = getAddress(outputBuffer);
outputAddress = address + outputBuffer.position();
outputLimit = address + outputBuffer.limit();
}
else if (output.hasArray()) {
outputBase = output.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit();
else if (outputBuffer.hasArray()) {
outputBase = outputBuffer.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName());
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + outputBuffer.getClass().getName());
}

// HACK: Assure JVM does not collect Slice wrappers while compressing, since the
// collection may trigger freeing of the underlying memory resulting in a segfault
// There is no other known way to signal to the JVM that an object should not be
// collected in a block, and technically, the JVM is allowed to eliminate these locks.
synchronized (input) {
synchronized (output) {
synchronized (inputBuffer) {
synchronized (outputBuffer) {
int written = Lz4RawCompressor.compress(
inputBase,
inputAddress,
Expand All @@ -97,7 +105,7 @@ else if (output.hasArray()) {
outputAddress,
outputLimit - outputAddress,
table);
output.position(output.position() + written);
outputBuffer.position(outputBuffer.position() + written);
}
}
}
Expand Down
50 changes: 29 additions & 21 deletions src/main/java/io/airlift/compress/lz4/Lz4Decompressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.airlift.compress.Decompressor;
import io.airlift.compress.MalformedInputException;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import static io.airlift.compress.lz4.UnsafeUtil.getAddress;
Expand All @@ -40,50 +41,57 @@ public int decompress(byte[] input, int inputOffset, int inputLength, byte[] out
public void decompress(ByteBuffer input, ByteBuffer output)
throws MalformedInputException
{
// Java 9+ added an overload of various methods in ByteBuffer. When compiling with Java 11+ and targeting Java 8 bytecode
// the resulting signatures are invalid for JDK 8, so accesses below result in NoSuchMethodError. Accessing the
// methods through the interface class works around the problem
// Sidenote: we can't target "javac --release 8" because Unsafe is not available in the signature data for that profile
Buffer inputBuffer = input;
Buffer outputBuffer = output;

Object inputBase;
long inputAddress;
long inputLimit;
if (input.isDirect()) {
if (inputBuffer.isDirect()) {
inputBase = null;
long address = getAddress(input);
inputAddress = address + input.position();
inputLimit = address + input.limit();
long address = getAddress(inputBuffer);
inputAddress = address + inputBuffer.position();
inputLimit = address + inputBuffer.limit();
}
else if (input.hasArray()) {
inputBase = input.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit();
else if (inputBuffer.hasArray()) {
inputBase = inputBuffer.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName());
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + inputBuffer.getClass().getName());
}

Object outputBase;
long outputAddress;
long outputLimit;
if (output.isDirect()) {
if (outputBuffer.isDirect()) {
outputBase = null;
long address = getAddress(output);
outputAddress = address + output.position();
outputLimit = address + output.limit();
long address = getAddress(outputBuffer);
outputAddress = address + outputBuffer.position();
outputLimit = address + outputBuffer.limit();
}
else if (output.hasArray()) {
outputBase = output.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit();
else if (outputBuffer.hasArray()) {
outputBase = outputBuffer.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName());
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + outputBuffer.getClass().getName());
}

// HACK: Assure JVM does not collect Slice wrappers while decompressing, since the
// collection may trigger freeing of the underlying memory resulting in a segfault
// There is no other known way to signal to the JVM that an object should not be
// collected in a block, and technically, the JVM is allowed to eliminate these locks.
synchronized (input) {
synchronized (output) {
synchronized (inputBuffer) {
synchronized (outputBuffer) {
int written = Lz4RawDecompressor.decompress(inputBase, inputAddress, inputLimit, outputBase, outputAddress, outputLimit);
output.position(output.position() + written);
outputBuffer.position(outputBuffer.position() + written);
}
}
}
Expand Down
50 changes: 29 additions & 21 deletions src/main/java/io/airlift/compress/lzo/LzoCompressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

import io.airlift.compress.Compressor;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import static io.airlift.compress.lzo.LzoRawCompressor.MAX_TABLE_SIZE;
Expand Down Expand Up @@ -47,48 +48,55 @@ public int compress(byte[] input, int inputOffset, int inputLength, byte[] outpu
@Override
public void compress(ByteBuffer input, ByteBuffer output)
{
// Java 9+ added an overload of various methods in ByteBuffer. When compiling with Java 11+ and targeting Java 8 bytecode
// the resulting signatures are invalid for JDK 8, so accesses below result in NoSuchMethodError. Accessing the
// methods through the interface class works around the problem
// Sidenote: we can't target "javac --release 8" because Unsafe is not available in the signature data for that profile
Buffer inputBuffer = input;
Buffer outputBuffer = output;

Object inputBase;
long inputAddress;
long inputLimit;
if (input.isDirect()) {
if (inputBuffer.isDirect()) {
inputBase = null;
long address = getAddress(input);
inputAddress = address + input.position();
inputLimit = address + input.limit();
long address = getAddress(inputBuffer);
inputAddress = address + inputBuffer.position();
inputLimit = address + inputBuffer.limit();
}
else if (input.hasArray()) {
inputBase = input.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit();
else if (inputBuffer.hasArray()) {
inputBase = inputBuffer.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName());
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + inputBuffer.getClass().getName());
}

Object outputBase;
long outputAddress;
long outputLimit;
if (output.isDirect()) {
if (outputBuffer.isDirect()) {
outputBase = null;
long address = getAddress(output);
outputAddress = address + output.position();
outputLimit = address + output.limit();
long address = getAddress(outputBuffer);
outputAddress = address + outputBuffer.position();
outputLimit = address + outputBuffer.limit();
}
else if (output.hasArray()) {
outputBase = output.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit();
else if (outputBuffer.hasArray()) {
outputBase = outputBuffer.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName());
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + outputBuffer.getClass().getName());
}

// HACK: Assure JVM does not collect Slice wrappers while compressing, since the
// collection may trigger freeing of the underlying memory resulting in a segfault
// There is no other known way to signal to the JVM that an object should not be
// collected in a block, and technically, the JVM is allowed to eliminate these locks.
synchronized (input) {
synchronized (output) {
synchronized (inputBuffer) {
synchronized (outputBuffer) {
int written = LzoRawCompressor.compress(
inputBase,
inputAddress,
Expand All @@ -97,7 +105,7 @@ else if (output.hasArray()) {
outputAddress,
outputLimit - outputAddress,
table);
output.position(output.position() + written);
outputBuffer.position(outputBuffer.position() + written);
}
}
}
Expand Down
50 changes: 29 additions & 21 deletions src/main/java/io/airlift/compress/lzo/LzoDecompressor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import io.airlift.compress.Decompressor;
import io.airlift.compress.MalformedInputException;

import java.nio.Buffer;
import java.nio.ByteBuffer;

import static io.airlift.compress.lzo.UnsafeUtil.getAddress;
Expand All @@ -40,50 +41,57 @@ public int decompress(byte[] input, int inputOffset, int inputLength, byte[] out
public void decompress(ByteBuffer input, ByteBuffer output)
throws MalformedInputException
{
// Java 9+ added an overload of various methods in ByteBuffer. When compiling with Java 11+ and targeting Java 8 bytecode
// the resulting signatures are invalid for JDK 8, so accesses below result in NoSuchMethodError. Accessing the
// methods through the interface class works around the problem
// Sidenote: we can't target "javac --release 8" because Unsafe is not available in the signature data for that profile
Buffer inputBuffer = input;
Buffer outputBuffer = output;

Object inputBase;
long inputAddress;
long inputLimit;
if (input.isDirect()) {
if (inputBuffer.isDirect()) {
inputBase = null;
long address = getAddress(input);
inputAddress = address + input.position();
inputLimit = address + input.limit();
long address = getAddress(inputBuffer);
inputAddress = address + inputBuffer.position();
inputLimit = address + inputBuffer.limit();
}
else if (input.hasArray()) {
inputBase = input.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + input.arrayOffset() + input.limit();
else if (inputBuffer.hasArray()) {
inputBase = inputBuffer.array();
inputAddress = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.position();
inputLimit = ARRAY_BYTE_BASE_OFFSET + inputBuffer.arrayOffset() + inputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + input.getClass().getName());
throw new IllegalArgumentException("Unsupported input ByteBuffer implementation " + inputBuffer.getClass().getName());
}

Object outputBase;
long outputAddress;
long outputLimit;
if (output.isDirect()) {
if (outputBuffer.isDirect()) {
outputBase = null;
long address = getAddress(output);
outputAddress = address + output.position();
outputLimit = address + output.limit();
long address = getAddress(outputBuffer);
outputAddress = address + outputBuffer.position();
outputLimit = address + outputBuffer.limit();
}
else if (output.hasArray()) {
outputBase = output.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + output.arrayOffset() + output.limit();
else if (outputBuffer.hasArray()) {
outputBase = outputBuffer.array();
outputAddress = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.position();
outputLimit = ARRAY_BYTE_BASE_OFFSET + outputBuffer.arrayOffset() + outputBuffer.limit();
}
else {
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + output.getClass().getName());
throw new IllegalArgumentException("Unsupported output ByteBuffer implementation " + outputBuffer.getClass().getName());
}

// HACK: Assure JVM does not collect Slice wrappers while decompressing, since the
// collection may trigger freeing of the underlying memory resulting in a segfault
// There is no other known way to signal to the JVM that an object should not be
// collected in a block, and technically, the JVM is allowed to eliminate these locks.
synchronized (input) {
synchronized (output) {
synchronized (inputBuffer) {
synchronized (outputBuffer) {
int written = LzoRawDecompressor.decompress(inputBase, inputAddress, inputLimit, outputBase, outputAddress, outputLimit);
output.position(output.position() + written);
outputBuffer.position(outputBuffer.position() + written);
}
}
}
Expand Down
Loading

0 comments on commit 181f668

Please sign in to comment.